diff --git a/UnityProjects/MRTKDevTemplate/Assets/Scripts/SystemKeyboardExample.cs b/UnityProjects/MRTKDevTemplate/Assets/Scripts/SystemKeyboardExample.cs
index a5bc39a78..77c0f9218 100644
--- a/UnityProjects/MRTKDevTemplate/Assets/Scripts/SystemKeyboardExample.cs
+++ b/UnityProjects/MRTKDevTemplate/Assets/Scripts/SystemKeyboardExample.cs
@@ -1,10 +1,6 @@
// Copyright (c) Mixed Reality Toolkit Contributors
// Licensed under the BSD 3-Clause
-// Disable "missing XML comment" warning for samples. While nice to have, this XML documentation is not required for samples.
-#pragma warning disable CS1591
-
-using MixedReality.Toolkit.Input;
using MixedReality.Toolkit.UX;
using TMPro;
using UnityEngine;
@@ -30,10 +26,8 @@ public class SystemKeyboardExample : MonoBehaviour
[SerializeField]
private TextMeshPro debugMessage = null;
-#pragma warning disable 0414
[SerializeField]
private KeyboardPreview mixedRealityKeyboardPreview = null;
-#pragma warning restore 0414
///
/// Opens a platform specific keyboard.
@@ -43,7 +37,10 @@ public void OpenSystemKeyboard()
#if WINDOWS_UWP
wmrKeyboard.ShowKeyboard(wmrKeyboard.Text, false);
#elif UNITY_IOS || UNITY_ANDROID
- touchscreenKeyboard = TouchScreenKeyboard.Open(string.Empty, TouchScreenKeyboardType.Default, false, false, false, false);
+ if (TouchScreenKeyboard.isSupported)
+ {
+ touchscreenKeyboard = TouchScreenKeyboard.Open(string.Empty, TouchScreenKeyboardType.Default, false, false, false, false);
+ }
#endif
}
@@ -51,7 +48,7 @@ public void OpenSystemKeyboard()
///
/// A Unity event function that is called on the frame when a script is enabled just before any of the update methods are called the first time.
- ///
+ ///
private void Start()
{
// Initially hide the preview.
@@ -91,7 +88,6 @@ private void Start()
#endif
}
-
#if WINDOWS_UWP
///
/// A Unity event function that is called every frame, if this object is enabled.
@@ -149,19 +145,19 @@ private void Update()
// touch screen keyboard.
if (touchscreenKeyboard != null)
{
- string KeyboardText = touchscreenKeyboard.text;
+ string keyboardText = touchscreenKeyboard.text;
if (TouchScreenKeyboard.visible)
{
if (debugMessage != null)
{
- debugMessage.text = "typing... " + KeyboardText;
+ debugMessage.text = "typing... " + keyboardText;
}
}
else
{
if (debugMessage != null)
{
- debugMessage.text = "typed " + KeyboardText;
+ debugMessage.text = "typed " + keyboardText;
}
touchscreenKeyboard = null;
@@ -173,4 +169,3 @@ private void Update()
#endregion MonoBehaviour Implementation
}
}
-#pragma warning restore CS1591
\ No newline at end of file
diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock
index cb4fc54ab..bfab914b5 100644
--- a/docs/Gemfile.lock
+++ b/docs/Gemfile.lock
@@ -1,7 +1,7 @@
GEM
remote: https://rubygems.org/
specs:
- addressable (2.8.8)
+ addressable (2.9.0)
public_suffix (>= 2.0.2, < 8.0)
base64 (0.3.0)
bigdecimal (3.3.1)
@@ -66,7 +66,7 @@ GEM
mercenary (0.4.0)
pathutil (0.16.2)
forwardable-extended (~> 2.6)
- public_suffix (7.0.0)
+ public_suffix (7.0.5)
rake (13.3.1)
rb-fsevent (0.11.2)
rb-inotify (0.11.1)
diff --git a/org.mixedrealitytoolkit.core/CHANGELOG.md b/org.mixedrealitytoolkit.core/CHANGELOG.md
index 0bc7ca7c2..0e5dbcf58 100644
--- a/org.mixedrealitytoolkit.core/CHANGELOG.md
+++ b/org.mixedrealitytoolkit.core/CHANGELOG.md
@@ -7,6 +7,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
### Added
* Added new `editorDefault` field for debugging the various `DisplayType`s in-editor. [PR #1130](https://github.com/MixedRealityToolkit/MixedRealityToolkit-Unity/pull/1130)
+* Added edit mode tests for `AssemblyExtensions`, `SystemType`, and `SerializableDictionary`. [PR #1122](https://github.com/MixedRealityToolkit/MixedRealityToolkit-Unity/pull/1122)
+
+### Changed
+
+* Updated `PackageValidator` to only be valid if `UNITY_EDITOR` is true. [PR #1125](https://github.com/MixedRealityToolkit/MixedRealityToolkit-Unity/pull/1125)
+* `AssemblyExtensions.GetLoadableTypes` now throws `ArgumentNullException` when called on a null assembly instead of `NullReferenceException`. [PR #1122](https://github.com/MixedRealityToolkit/MixedRealityToolkit-Unity/pull/1122)
+* Updated `MRTKBaseInteractable` to follow MRTK style. [PR #1073](https://github.com/MixedRealityToolkit/MixedRealityToolkit-Unity/pull/1073)
## [4.0.0-pre.3] - 2026-05-20
@@ -21,7 +28,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
### Fixed
-* Fixed "The type MixedReality.Toolkit.Core MixedReality.Toolkit.Experimental.BubbleChildHoverEvents/TrickleChildHoverEvents/BubbleChildSelectEvents/TrickleChildSelectEvents is being serialized by `[SerializeReference]`, but is missing the `[Serializable]` attribute." on Unity 6.3. [PR #1107](https://github.com/MixedRealityToolkit/MixedRealityToolkit-Unity/pull/1107)
+* Fixed "The type `MixedReality.Toolkit.Core MixedReality.Toolkit.Experimental.BubbleChildHoverEvents/TrickleChildHoverEvents/BubbleChildSelectEvents/TrickleChildSelectEvents` is being serialized by `[SerializeReference]`, but is missing the `[Serializable]` attribute." on Unity 6.3. [PR #1107](https://github.com/MixedRealityToolkit/MixedRealityToolkit-Unity/pull/1107)
### Deprecated
diff --git a/org.mixedrealitytoolkit.core/Interactables/MRTKBaseInteractable.cs b/org.mixedrealitytoolkit.core/Interactables/MRTKBaseInteractable.cs
index 55849d28a..c5e953598 100644
--- a/org.mixedrealitytoolkit.core/Interactables/MRTKBaseInteractable.cs
+++ b/org.mixedrealitytoolkit.core/Interactables/MRTKBaseInteractable.cs
@@ -19,7 +19,7 @@ public class MRTKBaseInteractable : XRBaseInteractable
{
#region Gaze
- readonly List hoveringGazeInteractors = new List();
+ private readonly List hoveringGazeInteractors = new List();
///
/// (Read Only) The list of components currently gazing this object.
@@ -30,14 +30,14 @@ public class MRTKBaseInteractable : XRBaseInteractable
#region GazePinch
- readonly List hoveringGazePinchInteractors = new List();
+ private readonly List hoveringGazePinchInteractors = new List();
///
/// (Read Only) The list of components currently hovering this object.
///
public List HoveringGazePinchInteractors => hoveringGazePinchInteractors;
- readonly List selectingGazePinchInteractors = new List();
+ private readonly List selectingGazePinchInteractors = new List();
///
/// (Read Only) The list of components currently selecting this object.
@@ -48,7 +48,7 @@ public class MRTKBaseInteractable : XRBaseInteractable
#region Poke
- readonly List hoveringPokeInteractors = new List();
+ private readonly List hoveringPokeInteractors = new List();
///
/// (Read Only) The list of components currently hovering this object.
@@ -59,14 +59,14 @@ public class MRTKBaseInteractable : XRBaseInteractable
#region Grab
- readonly List hoveringGrabInteractors = new List();
+ private readonly List hoveringGrabInteractors = new List();
///
/// (Read Only) The list of components currently hovering this object.
/// ]
public List HoveringGrabInteractors => hoveringGrabInteractors;
- readonly List selectingGrabInteractors = new List();
+ private readonly List selectingGrabInteractors = new List();
///
/// (Read Only) The list of components currently selecting this object.
@@ -77,7 +77,7 @@ public class MRTKBaseInteractable : XRBaseInteractable
#region Ray
- readonly List hoveringRayInteractors = new List();
+ private readonly List hoveringRayInteractors = new List();
///
/// (Read Only) The list of components currently hovering this object.
@@ -95,7 +95,7 @@ public class MRTKBaseInteractable : XRBaseInteractable
///
/// Is this object selected by a gaze-pinch interactor?
///
- public TimedFlag IsGazePinchSelected { get => isGazePinchSelected; }
+ public TimedFlag IsGazePinchSelected => isGazePinchSelected;
[SerializeField]
[Tooltip("Is this object selected by a non-gaze ray interactor?")]
@@ -104,7 +104,7 @@ public class MRTKBaseInteractable : XRBaseInteractable
///
/// Is this object selected by a non-gaze ray interactor?
///
- public TimedFlag IsRaySelected { get => isRaySelected; }
+ public TimedFlag IsRaySelected => isRaySelected;
[SerializeField]
[Tooltip("Is this object selected by a poke interactor?")]
@@ -113,7 +113,7 @@ public class MRTKBaseInteractable : XRBaseInteractable
///
/// Is this object selected by a poke interactor?
///
- public TimedFlag IsPokeSelected { get => isPokeSelected; }
+ public TimedFlag IsPokeSelected => isPokeSelected;
[SerializeField]
[Tooltip("Is this object selected by a grab interactor?")]
@@ -122,7 +122,7 @@ public class MRTKBaseInteractable : XRBaseInteractable
///
/// Is this object selected by a grab interactor?
///
- public TimedFlag IsGrabSelected { get => isGrabSelected; }
+ public TimedFlag IsGrabSelected => isGrabSelected;
[SerializeField]
[Tooltip("Is this object hovered by any gaze interactor?")]
@@ -131,7 +131,7 @@ public class MRTKBaseInteractable : XRBaseInteractable
///
/// Is this object hovered by any gaze interactor?
///
- public TimedFlag IsGazeHovered { get => isGazeHovered; }
+ public TimedFlag IsGazeHovered => isGazeHovered;
[SerializeField]
[Tooltip("Is this object hovered by a gaze-pinch interactor?")]
@@ -140,7 +140,7 @@ public class MRTKBaseInteractable : XRBaseInteractable
///
/// Is this object hovered by a gaze-pinch interactor?
///
- public TimedFlag IsGazePinchHovered { get => isGazePinchHovered; }
+ public TimedFlag IsGazePinchHovered => isGazePinchHovered;
[SerializeField]
[Tooltip("Is this object hovered by a non-gaze ray interactor?")]
@@ -149,7 +149,7 @@ public class MRTKBaseInteractable : XRBaseInteractable
///
/// Is this object hovered by a non-gaze ray interactor?
///
- public TimedFlag IsRayHovered { get => isRayHovered; }
+ public TimedFlag IsRayHovered => isRayHovered;
[SerializeField]
[Tooltip("Is this object hovered by a grab interactor?")]
@@ -158,7 +158,7 @@ public class MRTKBaseInteractable : XRBaseInteractable
///
/// Is this object hovered by a grab interactor?
///
- public TimedFlag IsGrabHovered { get => isGrabHovered; }
+ public TimedFlag IsGrabHovered => isGrabHovered;
[SerializeField]
[Tooltip("Is this object hovered by a near touch/poke interactor?")]
@@ -168,12 +168,12 @@ public class MRTKBaseInteractable : XRBaseInteractable
///
/// Is this object hovered by a near touch/poke interactor?
///
- public TimedFlag IsPokeHovered { get => isPokeHovered; }
+ public TimedFlag IsPokeHovered => isPokeHovered;
///
/// Is this object hovered by any interactor other than passive targeting interactors?
///
- public TimedFlag IsActiveHovered { get => isActiveHovered; }
+ public TimedFlag IsActiveHovered => isActiveHovered;
[SerializeField]
[Tooltip("Is this object hovered by any interactor other than only passive targeting interactors?")]
@@ -201,7 +201,7 @@ public void DisableInteractorType(SystemInterfaceType type)
}
}
///
- /// Removes the specified type to the set of interactors which cannot select this interactable
+ /// Removes the specified type from the set of interactors which cannot select this interactable
///
public void EnableInteractorType(SystemInterfaceType type)
{
diff --git a/org.mixedrealitytoolkit.core/Tests/Editor/AssemblyExtensionsTests.cs b/org.mixedrealitytoolkit.core/Tests/Editor/AssemblyExtensionsTests.cs
new file mode 100644
index 000000000..0ceffcba0
--- /dev/null
+++ b/org.mixedrealitytoolkit.core/Tests/Editor/AssemblyExtensionsTests.cs
@@ -0,0 +1,37 @@
+// Copyright (c) Mixed Reality Toolkit Contributors
+// Licensed under the BSD 3-Clause
+
+using NUnit.Framework;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+
+namespace MixedReality.Toolkit.Core.Tests.EditMode
+{
+ ///
+ /// Unit tests for AssemblyExtensions.
+ /// These run outside of PlayMode and do not require Unity engine initialization.
+ ///
+ public class AssemblyExtensionsTests
+ {
+ [Test]
+ public void GetLoadableTypes_ReturnsTypes_ForValidAssembly()
+ {
+ Assembly currentAssembly = Assembly.GetExecutingAssembly();
+
+ IEnumerable types = currentAssembly.GetLoadableTypes();
+
+ Assert.IsNotNull(types, "GetLoadableTypes should never return null.");
+ Assert.Greater(types.Count(), 0, "GetLoadableTypes should return the types within the executing assembly.");
+ Assert.IsTrue(types.Contains(typeof(AssemblyExtensionsTests)), "GetLoadableTypes failed to return known loadable types.");
+ }
+
+ [Test]
+ public void GetLoadableTypes_HandlesNullAssembly_Safely()
+ {
+ Assembly nullAssembly = null;
+ Assert.Throws(() => nullAssembly.GetLoadableTypes());
+ }
+ }
+}
diff --git a/org.mixedrealitytoolkit.core/Tests/Editor/AssemblyExtensionsTests.cs.meta b/org.mixedrealitytoolkit.core/Tests/Editor/AssemblyExtensionsTests.cs.meta
new file mode 100644
index 000000000..46f2addd4
--- /dev/null
+++ b/org.mixedrealitytoolkit.core/Tests/Editor/AssemblyExtensionsTests.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 32e95d882d3d7c44ca4f53530fed61b0
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/org.mixedrealitytoolkit.core/Tests/Editor/SerializableDictionaryTests.cs b/org.mixedrealitytoolkit.core/Tests/Editor/SerializableDictionaryTests.cs
new file mode 100644
index 000000000..7f99497c5
--- /dev/null
+++ b/org.mixedrealitytoolkit.core/Tests/Editor/SerializableDictionaryTests.cs
@@ -0,0 +1,87 @@
+// Copyright (c) Mixed Reality Toolkit Contributors
+// Licensed under the BSD 3-Clause
+
+using NUnit.Framework;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace MixedReality.Toolkit.Core.Tests.EditMode
+{
+ ///
+ /// Unit tests for SerializableDictionary.
+ /// These run outside of PlayMode and do not require Unity engine initialization.
+ ///
+ public class SerializableDictionaryTests
+ {
+ [Test]
+ public void Serialization_RestoresDictionary_FromInternalEntries()
+ {
+ var dict = new SerializableDictionary();
+ dict.Add("Key1", 100);
+ dict.Add("Key2", 200);
+
+ ISerializationCallbackReceiver receiver = dict;
+
+ // Simulate Unity preparing to serialize the object (populates the internal 'entries' list)
+ receiver.OnBeforeSerialize();
+
+ // Clear the base dictionary to simulate starting fresh after deserialization
+ ((Dictionary)dict).Clear();
+ Assert.AreEqual(0, dict.Count, "Base dictionary should be empty before deserialization.");
+
+ // Simulate Unity finishing deserialization (repopulates the dictionary from 'entries')
+ receiver.OnAfterDeserialize();
+
+ Assert.AreEqual(2, dict.Count, "Dictionary should have restored 2 items.");
+ Assert.AreEqual(100, dict["Key1"]);
+ Assert.AreEqual(200, dict["Key2"]);
+ }
+
+ [Test]
+ public void EditorOverride_Clear_RemovesAllSerializedEntries()
+ {
+ var dict = new SerializableDictionary();
+ dict.Add("Key1", 100);
+
+ ISerializationCallbackReceiver receiver = dict;
+ receiver.OnBeforeSerialize(); // Populate internal list
+
+ // Call the custom overridden Clear()
+ dict.Clear();
+
+ // Attempt to deserialize (which would normally restore Key1 if the internal list wasn't cleared)
+ receiver.OnAfterDeserialize();
+
+ Assert.AreEqual(0, dict.Count, "Dictionary should remain empty because the internal serialized entries were cleared.");
+ }
+
+ [Test]
+ public void EditorOverride_Remove_RemovesSpecificSerializedEntry()
+ {
+ var dict = new SerializableDictionary();
+ dict.Add("A", 1);
+ dict.Add("B", 2);
+ dict.Add("C", 3);
+
+ ISerializationCallbackReceiver receiver = dict;
+ receiver.OnBeforeSerialize();
+
+ // Use the overridden remove method which should also remove from the internal list
+ bool removedB = dict.Remove("B");
+ bool removedC = dict.Remove("C", out int valC);
+
+ // Clear the dictionary and restore from the serialized list to verify they are gone
+ ((Dictionary)dict).Clear();
+ receiver.OnAfterDeserialize();
+
+ Assert.IsTrue(removedB, "Remove(key) should return true for existing key.");
+ Assert.IsTrue(removedC, "Remove(key, out val) should return true for existing key.");
+ Assert.AreEqual(3, valC, "Remove(key, out val) should output the correct value.");
+
+ Assert.AreEqual(1, dict.Count, "Only 1 item should remain after deserialization.");
+ Assert.IsTrue(dict.ContainsKey("A"), "Key 'A' should have been preserved.");
+ Assert.IsFalse(dict.ContainsKey("B"), "Key 'B' should have been permanently removed.");
+ Assert.IsFalse(dict.ContainsKey("C"), "Key 'C' should have been permanently removed.");
+ }
+ }
+}
diff --git a/org.mixedrealitytoolkit.core/Tests/Editor/SerializableDictionaryTests.cs.meta b/org.mixedrealitytoolkit.core/Tests/Editor/SerializableDictionaryTests.cs.meta
new file mode 100644
index 000000000..77e68063f
--- /dev/null
+++ b/org.mixedrealitytoolkit.core/Tests/Editor/SerializableDictionaryTests.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 2801ac5575b6e5044ac92e55fe647979
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/org.mixedrealitytoolkit.core/Tests/Editor/SystemTypeTests.cs b/org.mixedrealitytoolkit.core/Tests/Editor/SystemTypeTests.cs
new file mode 100644
index 000000000..a1d62be59
--- /dev/null
+++ b/org.mixedrealitytoolkit.core/Tests/Editor/SystemTypeTests.cs
@@ -0,0 +1,116 @@
+// Copyright (c) Mixed Reality Toolkit Contributors
+// Licensed under the BSD 3-Clause
+
+using NUnit.Framework;
+using System;
+using UnityEngine;
+using UnityEngine.TestTools;
+
+namespace MixedReality.Toolkit.Core.Tests.EditMode
+{
+ ///
+ /// Unit tests for SystemType.
+ /// These run outside of PlayMode and do not require Unity engine initialization.
+ ///
+ public class SystemTypeTests
+ {
+ [Test]
+ public void GetReference_FromType_ReturnsValidString()
+ {
+ string reference = SystemType.GetReference(typeof(Vector3));
+
+ Assert.IsFalse(string.IsNullOrEmpty(reference));
+ Assert.IsTrue(reference.Contains("UnityEngine.Vector3"));
+ Assert.IsTrue(reference.Contains("UnityEngine.CoreModule"));
+ }
+
+ [Test]
+ public void GetReference_NullOrEmpty_ReturnsEmptyString()
+ {
+ Assert.AreEqual(string.Empty, SystemType.GetReference((Type)null));
+ Assert.AreEqual(string.Empty, SystemType.GetReference((string)null));
+ Assert.AreEqual(string.Empty, SystemType.GetReference(string.Empty));
+ }
+
+ [Test]
+ public void Constructors_SetPropertiesCorrectly()
+ {
+ Type targetType = typeof(int);
+
+ // Initialize from Type
+ SystemType fromType = new SystemType(targetType);
+ Assert.AreEqual(targetType, fromType.Type);
+ Assert.AreEqual(SystemType.GetReference(targetType), (string)fromType);
+
+ // Initialize from AssemblyQualifiedName
+ SystemType fromString = new SystemType(targetType.AssemblyQualifiedName);
+ Assert.AreEqual(targetType, fromString.Type);
+ }
+
+ [Test]
+ public void Constructor_AbstractType_SetsTypeToNull()
+ {
+ // SystemType is intentionally designed to nullify abstract types when initialized via string
+ SystemType fromString = new SystemType(typeof(Array).AssemblyQualifiedName);
+ Assert.IsNull(fromString.Type, "SystemType should not allow abstract types when initialized from an assembly string.");
+ }
+
+ [Test]
+ public void InvalidTypeAssignment_LogsError_ButSetsType()
+ {
+ SystemType sysType = new SystemType(typeof(int));
+
+ // Enums violate the ValidConstraint. SystemType logs an error but still completes the assignment.
+ LogAssert.Expect(LogType.Error, $"'{typeof(DayOfWeek).FullName}' is not a valid class or struct type.");
+
+ sysType.Type = typeof(DayOfWeek);
+
+ Assert.AreEqual(typeof(DayOfWeek), sysType.Type);
+ }
+
+ [Test]
+ public void ImplicitConversions_WorkCorrectly()
+ {
+ Type originalType = typeof(string);
+
+ // Type -> SystemType
+ SystemType sysType = originalType;
+ Assert.IsNotNull(sysType);
+
+ // SystemType -> Type
+ Type convertedType = sysType;
+ Assert.AreEqual(originalType, convertedType);
+
+ // SystemType -> string
+ string reference = sysType;
+ Assert.AreEqual(SystemType.GetReference(originalType), reference);
+ }
+
+ [Test]
+ public void Equality_MatchesSameTypes()
+ {
+ SystemType type1 = new SystemType(typeof(float));
+ SystemType type2 = new SystemType(typeof(float));
+ SystemType type3 = new SystemType(typeof(double));
+
+ Assert.IsTrue(type1.Equals(type2));
+ Assert.IsFalse(type1.Equals(type3));
+ Assert.IsFalse(type1.Equals(null));
+
+ // HashCodes should match for identical references
+ Assert.AreEqual(type1.GetHashCode(), type2.GetHashCode());
+ }
+
+ [Test]
+ public void Serialization_RestoresType()
+ {
+ var sysType = new SystemType(typeof(int));
+ ISerializationCallbackReceiver receiver = sysType;
+
+ // SystemType uses OnAfterDeserialize to re-establish the `type` mapping from the string `reference`
+ receiver.OnAfterDeserialize();
+
+ Assert.AreEqual(typeof(int), sysType.Type);
+ }
+ }
+}
diff --git a/org.mixedrealitytoolkit.core/Tests/Editor/SystemTypeTests.cs.meta b/org.mixedrealitytoolkit.core/Tests/Editor/SystemTypeTests.cs.meta
new file mode 100644
index 000000000..f6e786970
--- /dev/null
+++ b/org.mixedrealitytoolkit.core/Tests/Editor/SystemTypeTests.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: bff9d511c00e10941baf652aeb49d861
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/org.mixedrealitytoolkit.core/Tests/TestUtilities/PackageValidator.cs b/org.mixedrealitytoolkit.core/Tests/TestUtilities/PackageValidator.cs
index e7f775918..264bfe5be 100644
--- a/org.mixedrealitytoolkit.core/Tests/TestUtilities/PackageValidator.cs
+++ b/org.mixedrealitytoolkit.core/Tests/TestUtilities/PackageValidator.cs
@@ -1,9 +1,7 @@
// Copyright (c) Mixed Reality Toolkit Contributors
// Licensed under the BSD 3-Clause
-// Disable "missing XML comment" warning for tests. While nice to have, this documentation is not required.
-#pragma warning disable CS1591
-#if HAS_ASSET_STORE_VALIDATION
+#if HAS_ASSET_STORE_VALIDATION && UNITY_EDITOR
using System;
using System.Collections.Generic;
@@ -26,12 +24,7 @@ public static class PackageValidator
///
public static PackageValidatorResults Validate(string packageName)
{
- PackageInfo info = UpmPackageInfo(packageName);
- if (info == null)
- {
- throw new ArgumentException($"No package found with name \"{packageName}\"");
- }
-
+ PackageInfo info = UpmPackageInfo(packageName) ?? throw new ArgumentException($"No package found with name \"{packageName}\"");
string packageId = $"{info.name}@{info.version}";
ValidationSuite.ValidatePackage(packageId, ValidationType.AssetStore);
@@ -70,5 +63,5 @@ private static PackageInfo[] UpmListOffline(string packageIdOrName = null)
}
}
}
-#endif // HAS_ASSET_STORE_VALIDATION
-#pragma warning restore CS1591
+
+#endif // HAS_ASSET_STORE_VALIDATION && UNITY_EDITOR
diff --git a/org.mixedrealitytoolkit.core/Utilities/Extensions/AssemblyExtensions.cs b/org.mixedrealitytoolkit.core/Utilities/Extensions/AssemblyExtensions.cs
index 8eba33c1e..59e4586da 100644
--- a/org.mixedrealitytoolkit.core/Utilities/Extensions/AssemblyExtensions.cs
+++ b/org.mixedrealitytoolkit.core/Utilities/Extensions/AssemblyExtensions.cs
@@ -14,10 +14,15 @@ namespace MixedReality.Toolkit
public static class AssemblyExtensions
{
///
- /// Assembly.GetTypes() can throw in some cases. This extension will catch that exception and return only the types which were successfully loaded from the assembly.
+ /// Assembly.GetTypes() can throw in some cases. This extension will catch that exception and return only the types which were successfully loaded from the assembly.
///
public static IEnumerable GetLoadableTypes(this Assembly @this)
{
+ if (@this == null)
+ {
+ throw new ArgumentNullException(nameof(@this), "Assembly cannot be null.");
+ }
+
try
{
return @this.GetTypes();
diff --git a/org.mixedrealitytoolkit.input/CHANGELOG.md b/org.mixedrealitytoolkit.input/CHANGELOG.md
index da60bd152..255c57678 100644
--- a/org.mixedrealitytoolkit.input/CHANGELOG.md
+++ b/org.mixedrealitytoolkit.input/CHANGELOG.md
@@ -22,6 +22,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
* Updated `InteractionDetector` to work across all `XRRayInteractor` and `NearFarInteractor` implementations, instead of just MRTK-specific `MRTKRayInteractor` implementations. [PR #1090](https://github.com/MixedRealityToolkit/MixedRealityToolkit-Unity/pull/1090)
* Updated the minimum editor version to 6000.0.66f2 [PR #1112](https://github.com/MixedRealityToolkit/MixedRealityToolkit-Unity/pull/1112)
* Reserialized MRTK XR Rig prefab to remove stale serialized fields. [PR #1110](https://github.com/MixedRealityToolkit/MixedRealityToolkit-Unity/pull/1110)
+* Updated tests to follow existing MRTK test patterns. [PR #1073](https://github.com/MixedRealityToolkit/MixedRealityToolkit-Unity/pull/1073)
### Deprecated
diff --git a/org.mixedrealitytoolkit.input/Tests/Runtime/BasicInputTests.cs b/org.mixedrealitytoolkit.input/Tests/Runtime/BasicInputTests.cs
index 5a50837d4..038c7cebf 100644
--- a/org.mixedrealitytoolkit.input/Tests/Runtime/BasicInputTests.cs
+++ b/org.mixedrealitytoolkit.input/Tests/Runtime/BasicInputTests.cs
@@ -38,7 +38,7 @@ public class BasicInputTests : BaseRuntimeInputTests
/// Ensure the simulated input devices are registered and present.
///
[UnityTest]
- public IEnumerator InputDeviceSmoketest()
+ public IEnumerator InputDeviceSmokeTest()
{
foreach (var device in InputSystem.devices)
{
@@ -53,9 +53,10 @@ public IEnumerator InputDeviceSmoketest()
///
#pragma warning disable CS0618 // Adding this pragma because all the encompassed tests depend on deprecated ActionBasedController
[UnityTest]
- public IEnumerator InputBindingSmoketest()
+ public IEnumerator InputBindingSmokeTest()
{
- var controllers = new[] {
+ XRBaseController[] controllers =
+ {
CachedLookup.LeftHandController,
CachedLookup.RightHandController,
CachedLookup.GazeController
@@ -77,7 +78,7 @@ public IEnumerator InputBindingSmoketest()
/// Ensure the simulated input device actually makes the rig's controllers move/actuate.
///
[UnityTest]
- public IEnumerator HandMovingSmoketest()
+ public IEnumerator HandMovingSmokeTest()
{
var controller = CachedLookup.RightHandController as ActionBasedController;
@@ -116,7 +117,7 @@ public IEnumerator GrabAnchorTest()
Vector3 cubePos = InputTestUtilities.InFrontOfUser();
cube.transform.position = cubePos;
cube.transform.localScale = Vector3.one * 1.0f;
-
+
var testHand = new TestHand(Handedness.Right);
InputTestUtilities.SetHandAnchorPoint(Handedness.Right, ControllerAnchorPoint.Grab);
@@ -145,32 +146,28 @@ public IEnumerator GrabAnchorTest()
public IEnumerator StatefulInteractableSmokeTest()
{
GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
- cube.AddComponent();
+ StatefulInteractable firstCubeInteractable = cube.AddComponent();
cube.transform.position = InputTestUtilities.InFrontOfUser(new Vector3(0.2f, 0.2f, 0.5f));
cube.transform.localScale = Vector3.one * 0.1f;
// For this test, we won't use poke selection.
- cube.GetComponent().DisableInteractorType(typeof(PokeInteractor));
+ firstCubeInteractable.DisableInteractorType(typeof(PokeInteractor));
GameObject cube2 = GameObject.CreatePrimitive(PrimitiveType.Cube);
- cube2.AddComponent();
+ StatefulInteractable secondCubeInteractable = cube2.AddComponent();
cube2.transform.position = InputTestUtilities.InFrontOfUser(new Vector3(-0.2f, -0.2f, 0.5f));
cube2.transform.localScale = Vector3.one * 0.1f;
// For this test, we won't use poke selection.
- cube2.GetComponent().DisableInteractorType(typeof(PokeInteractor));
+ secondCubeInteractable.DisableInteractorType(typeof(PokeInteractor));
var rightHand = new TestHand(Handedness.Right);
yield return rightHand.Show(InputTestUtilities.InFrontOfUser(0.5f));
-
yield return RuntimeTestUtilities.WaitForUpdates();
bool shouldTestToggle = false;
- StatefulInteractable firstCubeInteractable = cube.GetComponent();
- StatefulInteractable secondCubeInteractable = cube2.GetComponent();
-
for (int i = 0; i < 5; i++)
{
// Flip this back and forth to test both toggleability and un-toggleability
@@ -185,27 +182,33 @@ public IEnumerator StatefulInteractableSmokeTest()
firstCubeInteractable.TriggerOnRelease = (i % 2) == 0;
Assert.IsFalse(firstCubeInteractable.IsGrabHovered,
+ "StatefulInteractable was already GrabHovered.");
+ Assert.IsFalse(firstCubeInteractable.isHovered,
"StatefulInteractable was already hovered.");
yield return rightHand.MoveTo(cube.transform.position);
yield return RuntimeTestUtilities.WaitForUpdates();
Assert.IsTrue(firstCubeInteractable.IsGrabHovered,
+ "StatefulInteractable did not get GrabHovered.");
+ Assert.IsTrue(firstCubeInteractable.isHovered,
"StatefulInteractable did not get hovered.");
yield return rightHand.SetHandshape(HandshapeId.Pinch);
yield return RuntimeTestUtilities.WaitForUpdates();
Assert.IsTrue(firstCubeInteractable.IsGrabSelected,
"StatefulInteractable did not get GrabSelected.");
+ Assert.IsTrue(firstCubeInteractable.isSelected,
+ "StatefulInteractable did not get selected.");
if (shouldTestToggle)
{
- if (secondCubeInteractable.TriggerOnRelease)
+ if (firstCubeInteractable.TriggerOnRelease)
{
- Assert.IsFalse(secondCubeInteractable.IsToggled, "StatefulInteractable toggled on press, when it was set to be toggled on release.");
+ Assert.IsFalse(firstCubeInteractable.IsToggled, "StatefulInteractable toggled on press, when it was set to be toggled on release.");
}
else
{
- Assert.IsFalse(secondCubeInteractable.IsToggled, "StatefulInteractable didn't toggled on press, when it was set to be toggled on press.");
+ Assert.IsFalse(firstCubeInteractable.IsToggled, "StatefulInteractable didn't toggled on press, when it was set to be toggled on press.");
}
}
@@ -234,17 +237,23 @@ public IEnumerator StatefulInteractableSmokeTest()
yield return RuntimeTestUtilities.WaitForUpdates();
Assert.IsFalse(secondCubeInteractable.IsGrabHovered,
+ "StatefulInteractable was already GrabHovered.");
+ Assert.IsFalse(secondCubeInteractable.isHovered,
"StatefulInteractable was already hovered.");
yield return rightHand.MoveTo(secondCubeInteractable.transform.position);
yield return RuntimeTestUtilities.WaitForUpdates();
Assert.IsTrue(secondCubeInteractable.IsGrabHovered,
+ "StatefulInteractable did not get GrabHovered.");
+ Assert.IsTrue(secondCubeInteractable.isHovered,
"StatefulInteractable did not get hovered.");
yield return rightHand.SetHandshape(HandshapeId.Pinch);
yield return RuntimeTestUtilities.WaitForUpdates();
Assert.IsTrue(secondCubeInteractable.IsGrabSelected,
"StatefulInteractable did not get GrabSelected.");
+ Assert.IsTrue(secondCubeInteractable.isSelected,
+ "StatefulInteractable did not get selected.");
if (shouldTestToggle)
{
@@ -312,7 +321,7 @@ public IEnumerator GazePinchSmokeTest()
yield return rightHand.SetHandshape(HandshapeId.Open);
yield return RuntimeTestUtilities.WaitForUpdates();
-
+
Assert.IsFalse(interactable.isSelected);
Assert.IsFalse(interactable.IsGazePinchSelected);
Assert.IsTrue(interactable.isHovered);
@@ -397,7 +406,7 @@ public IEnumerator ToggleHydrationTest()
didFireEvent = false;
interactable.ForceSetToggled(true, fireEvents: false);
-
+
Assert.IsTrue(interactable.IsToggled, "Interactable didn't get toggled.");
Assert.IsFalse(didFireEvent, "ForceSetToggled(true, fireEvents:false) should NOT have fired the event.");
@@ -503,7 +512,7 @@ public IEnumerator SpawnInteractableOnHand()
// Move hand far away.
yield return rightHand.MoveTo(new Vector3(2, 2, 2));
- yield return RuntimeTestUtilities.WaitForUpdates(frameCount:240);
+ yield return RuntimeTestUtilities.WaitForUpdates(frameCount: 240);
Assert.IsFalse(AnyProximityDetectorsTriggered(), "Prox detectors should no longer be triggered.");
diff --git a/org.mixedrealitytoolkit.input/Tests/Runtime/InteractionModeManagerTests.cs b/org.mixedrealitytoolkit.input/Tests/Runtime/InteractionModeManagerTests.cs
index 024559b4c..2eed33ade 100644
--- a/org.mixedrealitytoolkit.input/Tests/Runtime/InteractionModeManagerTests.cs
+++ b/org.mixedrealitytoolkit.input/Tests/Runtime/InteractionModeManagerTests.cs
@@ -84,7 +84,7 @@ public IEnumerator InteractionDetectorTest()
XRBaseController rightHandController = CachedLookup.RightHandController;
Assert.IsTrue(rightHandController != null, "No controllers found for right hand.");
- // Moving the hand to a position where it's far ray is hovering over the cube
+ // Moving the hand to a position where its far ray is hovering over the cube
yield return rightHand.AimAt(cube.transform.position);
yield return RuntimeTestUtilities.WaitForUpdates();
@@ -198,7 +198,7 @@ public IEnumerator ModeMediationTest()
ValidateInteractionModeActive(rightHandController, nearMode);
- // Moving the hand to a position where it's far ray is hovering over the cube
+ // Moving the hand to a position where its far ray is hovering over the cube
yield return rightHand.MoveTo(cube.transform.position + new Vector3(0.02f, -0.1f, -0.8f));
yield return RuntimeTestUtilities.WaitForUpdates(frameCount: 120);
diff --git a/org.mixedrealitytoolkit.input/Tests/Runtime/Utilities/InputTestUtilities.cs b/org.mixedrealitytoolkit.input/Tests/Runtime/Utilities/InputTestUtilities.cs
index 115c3c2c5..3865a0487 100644
--- a/org.mixedrealitytoolkit.input/Tests/Runtime/Utilities/InputTestUtilities.cs
+++ b/org.mixedrealitytoolkit.input/Tests/Runtime/Utilities/InputTestUtilities.cs
@@ -452,7 +452,7 @@ public static IEnumerator PointHandToTarget(Vector3 target, HandshapeId handshap
///
///
///
- /// This smooths the handshape based on the provided/ over the number of
+ /// This smooths the handshape based on the provided over the number of
/// steps provided by .
///
///
diff --git a/org.mixedrealitytoolkit.spatialmanipulation/CHANGELOG.md b/org.mixedrealitytoolkit.spatialmanipulation/CHANGELOG.md
index 243dc00fb..4e0f340f2 100644
--- a/org.mixedrealitytoolkit.spatialmanipulation/CHANGELOG.md
+++ b/org.mixedrealitytoolkit.spatialmanipulation/CHANGELOG.md
@@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
### Changed
* Updated the minimum editor version to 6000.0.66f2 [PR #1112](https://github.com/MixedRealityToolkit/MixedRealityToolkit-Unity/pull/1112)
+* Updated tests to follow existing MRTK test patterns. [PR #1073](https://github.com/MixedRealityToolkit/MixedRealityToolkit-Unity/pull/1073)
### Fixed
diff --git a/org.mixedrealitytoolkit.spatialmanipulation/Tests/Runtime/HandConstraintTests.cs b/org.mixedrealitytoolkit.spatialmanipulation/Tests/Runtime/HandConstraintTests.cs
index cfa274fe7..fb2a05341 100644
--- a/org.mixedrealitytoolkit.spatialmanipulation/Tests/Runtime/HandConstraintTests.cs
+++ b/org.mixedrealitytoolkit.spatialmanipulation/Tests/Runtime/HandConstraintTests.cs
@@ -24,7 +24,7 @@ public class HandConstraintTests : BaseRuntimeInputTests
[UnityTest]
public IEnumerator HandConstraintEventsOneHanded()
{
- // Disable gaze interactions for this unit test;
+ // Disable gaze interactions for this unit test
InputTestUtilities.DisableGazeInteractor();
// Set up GameObject with a SolverHandler
diff --git a/org.mixedrealitytoolkit.spatialmanipulation/Tests/Runtime/ObjectManipulatorTests.cs b/org.mixedrealitytoolkit.spatialmanipulation/Tests/Runtime/ObjectManipulatorTests.cs
index 9333f7234..9ef49ac43 100644
--- a/org.mixedrealitytoolkit.spatialmanipulation/Tests/Runtime/ObjectManipulatorTests.cs
+++ b/org.mixedrealitytoolkit.spatialmanipulation/Tests/Runtime/ObjectManipulatorTests.cs
@@ -4,20 +4,21 @@
// Disable "missing XML comment" warning for tests. While nice to have, this documentation is not required.
#pragma warning disable CS1591
-using MixedReality.Toolkit;
using MixedReality.Toolkit.Core.Tests;
-using MixedReality.Toolkit.Input.Tests;
+using MixedReality.Toolkit.Input;
using MixedReality.Toolkit.Input.Simulation;
+using MixedReality.Toolkit.Input.Tests;
using NUnit.Framework;
using System;
using System.Collections;
+using System.Collections.Generic;
using UnityEngine;
using UnityEngine.TestTools;
+using UnityEngine.XR.Interaction.Toolkit.Interactables;
using UnityEngine.XR.Interaction.Toolkit.Interactors;
using HandshapeId = MixedReality.Toolkit.Input.HandshapeTypes.HandshapeId;
using MovementType = UnityEngine.XR.Interaction.Toolkit.Interactables.XRBaseInteractable.MovementType;
-using MixedReality.Toolkit.Input;
namespace MixedReality.Toolkit.SpatialManipulation.Runtime.Tests
{
@@ -538,7 +539,6 @@ public IEnumerator ObjectManipulatorOneHandMoveFar()
}
}
-
///
/// This tests that the gaze pointer can be used to directly invoke the manipulation logic via simulated pointer events, used
/// for scenarios like voice-driven movement using the gaze pointer.
@@ -748,26 +748,24 @@ public IEnumerator TestObjManipTargetChange()
// Cube2 should be facing the user.
Assert.IsTrue(cube2.transform.forward.CloseEnoughTo(-(cube2.transform.position - Camera.main.transform.position).normalized), "Cube2 didn't stay facing user!");
-
}
#endregion
#region Two Handed Manipulation Tests
- // This test is not yet working due to some confusion as to how the centroid math works with the current object manipulator
-
- /*
///
/// Test that the grab centroid is calculated correctly while rotating
/// the hands during a two-hand near interaction grab.
///
- [UnityTest]
+ [UnityTest, Ignore("This test is not yet working due to some confusion as to how the centroid math works with the current object manipulator")]
public IEnumerator ObjectManipulatorTwoHandedCentroid()
{
InputTestUtilities.DisableGazeInteractor();
+ yield return RuntimeTestUtilities.WaitForUpdates();
InputTestUtilities.InitializeCameraToOriginAndForward();
+ yield return RuntimeTestUtilities.WaitForUpdates();
// Set up cube with ObjectManipulator
var testObject = GameObject.CreatePrimitive(PrimitiveType.Cube);
@@ -781,17 +779,21 @@ public IEnumerator ObjectManipulatorTwoHandedCentroid()
objectManipulator.SmoothingFar = false;
objectManipulator.SmoothingNear = false;
// Configuring for two-handed interaction
- objectManipulator.selectMode = UnityEngine.XR.Interaction.Toolkit.InteractableSelectMode.Multiple;
+ objectManipulator.selectMode = InteractableSelectMode.Multiple;
TestHand rightHand = new TestHand(Handedness.Right);
TestHand leftHand = new TestHand(Handedness.Left);
+ yield return RuntimeTestUtilities.WaitForUpdates();
yield return rightHand.Show(Vector3.zero);
+ yield return RuntimeTestUtilities.WaitForUpdates();
yield return leftHand.Show(Vector3.zero);
+ yield return RuntimeTestUtilities.WaitForUpdates();
yield return rightHand.MoveTo(new Vector3(0.1f, -0.1f, 0.8f));
+ yield return RuntimeTestUtilities.WaitForUpdates();
yield return leftHand.MoveTo(new Vector3(-0.1f, -0.1f, 0.8f));
- yield return null;
+ yield return RuntimeTestUtilities.WaitForUpdates();
// Only testing move/rotate centroid position
objectManipulator.AllowedManipulations = TransformFlags.Move | TransformFlags.Scale;
@@ -805,8 +807,10 @@ public IEnumerator ObjectManipulatorTwoHandedCentroid()
objectManipulator.selectExited.AddListener((med) => manipulationEndedCount++);
// Grab the box.
- yield return rightHand.SetGesture(GestureId.Pinch);
- yield return leftHand.SetGesture(GestureId.Pinch);
+ yield return rightHand.SetHandshape(HandshapeId.Pinch);
+ yield return RuntimeTestUtilities.WaitForUpdates();
+ yield return leftHand.SetHandshape(HandshapeId.Pinch);
+ yield return RuntimeTestUtilities.WaitForUpdates();
// Previously we checked that we didn't move after two pinches, however, due to the hand position shifting slighting on pinch, this is not applicable
// TODO, address in the future?
@@ -816,14 +820,11 @@ public IEnumerator ObjectManipulatorTwoHandedCentroid()
// The ObjectManipulator should recognize that we've begun manipulation.
Assert.IsTrue(manipulationStartedCount > 0);
- yield return RuntimeTestUtilities.WaitForEnterKey();
-
// Move both hands outwards; the object may be scaled but the position should remain the same.
yield return rightHand.MoveTo(new Vector3(0.2f, -0.1f, 0.8f));
+ yield return RuntimeTestUtilities.WaitForUpdates();
yield return leftHand.MoveTo(new Vector3(-0.2f, -0.1f, 0.8f));
-
-
- yield return RuntimeTestUtilities.WaitForEnterKey();
+ yield return RuntimeTestUtilities.WaitForUpdates();
// Should *still* not have moved!
// TestUtilities.AssertAboutEqual(testObject.transform.position, initialObjectPosition, $"Object moved when it shouldn't have! Position: {testObject.transform.position:F5}", 0.00001f);
@@ -854,8 +855,10 @@ public IEnumerator ObjectManipulatorTwoHandedCentroid()
yield return MoveHandsAndCheckCentroid(testCondition.Item1, testCondition.Item2, leftHand, rightHand, objectManipulator, initialObjectPosition, originalCentroid, testObject.transform);
}
- yield return rightHand.SetGesture(GestureId.Open);
- yield return leftHand.SetGesture(GestureId.Open);
+ yield return rightHand.SetHandshape(HandshapeId.Open);
+ yield return RuntimeTestUtilities.WaitForUpdates();
+ yield return leftHand.SetHandshape(HandshapeId.Open);
+ yield return RuntimeTestUtilities.WaitForUpdates();
}
///
@@ -871,11 +874,15 @@ private IEnumerator MoveHandsAndCheckCentroid(Vector3 handRotationEuler, Vector3
{
// Rotate the hands.
yield return rightHand.RotateTo(Quaternion.Euler(handRotationEuler.x, handRotationEuler.y, handRotationEuler.z));
+ yield return RuntimeTestUtilities.WaitForUpdates();
yield return leftHand.RotateTo(Quaternion.Euler(handRotationEuler.x, -handRotationEuler.y, -handRotationEuler.z));
+ yield return RuntimeTestUtilities.WaitForUpdates();
// Move the hands.
yield return rightHand.MoveTo(new Vector3(handPosition.x, handPosition.y, handPosition.z));
+ yield return RuntimeTestUtilities.WaitForUpdates();
yield return leftHand.MoveTo(new Vector3(-handPosition.x, handPosition.y, handPosition.z));
+ yield return RuntimeTestUtilities.WaitForUpdates();
// Recalculate the new grab centroid.
var leftGrabPoint = om.interactorsSelecting[0].transform.position;
@@ -889,7 +896,6 @@ private IEnumerator MoveHandsAndCheckCentroid(Vector3 handRotationEuler, Vector3
TestUtilities.AssertAboutEqual(testObject.transform.position, originalObjectPosition + centroidDelta,
$"Object moved did not move according to the delta! Actual position: {testObject.transform.position:F5}, should be {originalObjectPosition + centroidDelta}", 0.00001f);
}
- */
#endregion
diff --git a/org.mixedrealitytoolkit.uxcore/CHANGELOG.md b/org.mixedrealitytoolkit.uxcore/CHANGELOG.md
index a0e83bb1c..514019778 100644
--- a/org.mixedrealitytoolkit.uxcore/CHANGELOG.md
+++ b/org.mixedrealitytoolkit.uxcore/CHANGELOG.md
@@ -4,6 +4,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [4.0.0-pre.4] - 2026-06-10
+### Added
+
+* Added `AlphaBlend` tint mode to `TintEffect`. [PR #1131](https://github.com/MixedRealityToolkit/MixedRealityToolkit-Unity/pull/1131)
+
### Fixed
* Fixed build issue caused by `FontIconSelectorMigrationUtility` not being in an Editor-only assembly.
diff --git a/org.mixedrealitytoolkit.uxcore/StateVisualizer/Effects/TintEffect.cs b/org.mixedrealitytoolkit.uxcore/StateVisualizer/Effects/TintEffect.cs
index e13f08194..65a38cf6f 100644
--- a/org.mixedrealitytoolkit.uxcore/StateVisualizer/Effects/TintEffect.cs
+++ b/org.mixedrealitytoolkit.uxcore/StateVisualizer/Effects/TintEffect.cs
@@ -155,7 +155,6 @@ public override void PrepareFrame(Playable playable, FrameData info)
{
ApplyColor(startColors[i], tintables[i]);
}
-
}
///
@@ -185,6 +184,12 @@ public override void ProcessFrame(Playable playable, FrameData info, object play
{
targetColor = currentColor * TintColor;
}
+ else if (BlendMode == BlendType.AlphaBlend)
+ {
+ // Simulate layering a transparent color over the current color
+ targetColor = Color.Lerp(currentColor, TintColor, TintColor.a);
+ targetColor.a = currentColor.a; // Preserve the graphic's original overall opacity
+ }
else
{
targetColor = TintColor;
@@ -214,7 +219,12 @@ internal enum BlendType
///
/// Multiply the tint color onto the existing color stack.
///
- Multiply
+ Multiply,
+
+ ///
+ /// Alpha-blends the tint color onto the existing color stack using the tint color's alpha value.
+ ///
+ AlphaBlend
}
// Used internally to hint to the editor that this is a variable/float-based state.