SoftMaskEditor.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. using System;
  2. using UnityEditor;
  3. using UnityEngine;
  4. using UnityEngine.Assertions;
  5. namespace SoftMasking.Editor {
  6. [CustomEditor(typeof(SoftMask))]
  7. [CanEditMultipleObjects]
  8. public class SoftMaskEditor : UnityEditor.Editor {
  9. SerializedProperty _source;
  10. SerializedProperty _separateMask;
  11. SerializedProperty _sprite;
  12. SerializedProperty _spriteBorderMode;
  13. SerializedProperty _spritePixelsPerUnitMultiplier;
  14. SerializedProperty _texture;
  15. SerializedProperty _textureUVRect;
  16. SerializedProperty _channelWeights;
  17. SerializedProperty _raycastThreshold;
  18. SerializedProperty _invertMask;
  19. SerializedProperty _invertOutsides;
  20. bool _customWeightsExpanded;
  21. static class Labels {
  22. public static readonly GUIContent Source = new GUIContent("Source",
  23. "Specifies from where the mask gets its image.");
  24. public static readonly GUIContent Sprite = new GUIContent("Sprite",
  25. "Sprite that should be used as the mask image.");
  26. public static readonly GUIContent SpriteBorderMode = new GUIContent("Sprite Border Mode",
  27. "Specifies how sprite borders should be processed. Corresponds to Unity UI Image " +
  28. "types. The Sliced and Tiled modes are available only for sprites having borders.");
  29. public static readonly GUIContent SpritePixelsPerUnitMultiplier = new GUIContent("Pixels Per Unit Multiplier",
  30. "Specifies a multiplier that is applied to Sprite's Pixels Per Unit property.");
  31. public static readonly GUIContent Texture = new GUIContent("Texture",
  32. "Texture that should be used as the mask image.");
  33. public static readonly GUIContent TextureUVRect = new GUIContent("Texture UV Rect",
  34. "Specifies a normalized rectangle in UV-space defining an image part that " +
  35. "should be used as the mask image.");
  36. public static readonly GUIContent SeparateMask = new GUIContent("Separate Mask",
  37. "A Rect Transform that defines the bounds of the mask in the scene. If not set " +
  38. "bounds of Rect Transform attached to this object is used. Use of a separate Rect " +
  39. "Transform allows to move or resize mask without affecting children.");
  40. public static readonly GUIContent RaycastThreshold = new GUIContent("Raycast Threshold",
  41. "Specifies the minimum value that the mask should have at any given point to " +
  42. "pass a pointer input event to children. 0 makes the entire mask rectangle pass " +
  43. "events. 1 passes events only in points where masked objects are fully opaque.");
  44. public static readonly GUIContent MaskChannel = new GUIContent("Mask Channel",
  45. "Specifies which image channel(s) should be used as the mask source.");
  46. public static readonly GUIContent ChannelWeights = new GUIContent("Channel Weights",
  47. "Specifies how much value each of image channels adds to the resulted mask value. " +
  48. "The resulted mask value is calculated as the sum of products of image channel " +
  49. "values and the corresponding fields");
  50. public static readonly GUIContent R = new GUIContent("R");
  51. public static readonly GUIContent G = new GUIContent("G");
  52. public static readonly GUIContent B = new GUIContent("B");
  53. public static readonly GUIContent A = new GUIContent("A");
  54. public static readonly GUIContent InvertMask = new GUIContent("Invert Mask",
  55. "If set, the mask inside the mask rectangle will be inverted.");
  56. public static readonly GUIContent InvertOutsides = new GUIContent("Invert Outsides",
  57. "If set, the mask outside the mask rectangle will be inverted. By default everything " +
  58. "outside the mask is transparent, so when this flag is set, the outsides of the mask " +
  59. "are opaque.");
  60. public static readonly string UnsupportedShaders =
  61. "Some of children's shaders aren't supported. Mask won't work on these elements. " +
  62. "See the documentation for more details about how to add Soft Mask support to " +
  63. "custom shaders.";
  64. public static readonly string NestedMasks =
  65. "The mask may work not as expected because a child or parent Soft Mask exists. " +
  66. "Soft Mask doesn't support nesting. You can work around this limitation by nesting " +
  67. "a Soft Mask into a Unity standard Mask or Rect Mask 2D or vice versa.";
  68. public static readonly string TightPackedSprite =
  69. "Soft Mask doesn't support tight packed sprites. Disable packing for the mask sprite " +
  70. "or use Rectangle pack mode.";
  71. public static readonly string AlphaSplitSprite =
  72. "Soft Mask doesn't support sprites with an alpha split texture. Disable compression of " +
  73. "the sprite texture or use another compression type.";
  74. public static readonly string UnsupportedImageType =
  75. "Soft Mask doesn't support this image type. The supported image types are Simple, Sliced " +
  76. "and Tiled.";
  77. public static readonly string UnreadableTexture =
  78. "Soft Mask with Raycast Threshold greater than zero can't be used with a CPU-unreadable " +
  79. "texture. You can make the texture readable in the Texture Import Settings.";
  80. public static readonly string UnreadableRenderTexture =
  81. "Soft Mask with Raycast Threshold greater than zero can't be used with Render Textures " +
  82. "as they're unreadable for CPU. Only a Texture 2D may be used with the raycast filtering " +
  83. "capability.";
  84. }
  85. public void OnEnable() {
  86. _source = serializedObject.FindProperty("_source");
  87. _separateMask = serializedObject.FindProperty("_separateMask");
  88. _sprite = serializedObject.FindProperty("_sprite");
  89. _spriteBorderMode = serializedObject.FindProperty("_spriteBorderMode");
  90. _spritePixelsPerUnitMultiplier = serializedObject.FindProperty("_spritePixelsPerUnitMultiplier");
  91. _texture = serializedObject.FindProperty("_texture");
  92. _textureUVRect = serializedObject.FindProperty("_textureUVRect");
  93. _channelWeights = serializedObject.FindProperty("_channelWeights");
  94. _raycastThreshold = serializedObject.FindProperty("_raycastThreshold");
  95. _invertMask = serializedObject.FindProperty("_invertMask");
  96. _invertOutsides = serializedObject.FindProperty("_invertOutsides");
  97. Assert.IsNotNull(_source);
  98. Assert.IsNotNull(_separateMask);
  99. Assert.IsNotNull(_sprite);
  100. Assert.IsNotNull(_spriteBorderMode);
  101. Assert.IsNotNull(_spritePixelsPerUnitMultiplier);
  102. Assert.IsNotNull(_texture);
  103. Assert.IsNotNull(_textureUVRect);
  104. Assert.IsNotNull(_channelWeights);
  105. Assert.IsNotNull(_raycastThreshold);
  106. Assert.IsNotNull(_invertMask);
  107. Assert.IsNotNull(_invertOutsides);
  108. _customWeightsExpanded = CustomEditors.CustomChannelShouldBeExpandedFor(_channelWeights.colorValue);
  109. }
  110. public override void OnInspectorGUI() {
  111. serializedObject.Update();
  112. EditorGUILayout.PropertyField(_source, Labels.Source);
  113. CustomEditors.WithIndent(() => {
  114. switch ((SoftMask.MaskSource)_source.enumValueIndex) {
  115. case SoftMask.MaskSource.Graphic:
  116. break;
  117. case SoftMask.MaskSource.Sprite:
  118. EditorGUILayout.PropertyField(_sprite, Labels.Sprite);
  119. EditorGUILayout.PropertyField(_spriteBorderMode, Labels.SpriteBorderMode);
  120. if ((SoftMask.BorderMode)_spriteBorderMode.enumValueIndex != SoftMask.BorderMode.Simple)
  121. EditorGUILayout.PropertyField(_spritePixelsPerUnitMultiplier, Labels.SpritePixelsPerUnitMultiplier);
  122. break;
  123. case SoftMask.MaskSource.Texture:
  124. EditorGUILayout.PropertyField(_texture, Labels.Texture);
  125. EditorGUILayout.PropertyField(_textureUVRect, Labels.TextureUVRect);
  126. break;
  127. }
  128. });
  129. EditorGUILayout.PropertyField(_separateMask, Labels.SeparateMask);
  130. EditorGUILayout.Slider(_raycastThreshold, 0, 1, Labels.RaycastThreshold);
  131. EditorGUILayout.PropertyField(_invertMask, Labels.InvertMask);
  132. EditorGUILayout.PropertyField(_invertOutsides, Labels.InvertOutsides);
  133. var newExpanded = CustomEditors.ChannelWeights(Labels.MaskChannel, _channelWeights, _customWeightsExpanded);
  134. if (newExpanded != _customWeightsExpanded) {
  135. _customWeightsExpanded = newExpanded;
  136. Repaint();
  137. }
  138. ShowErrorsIfAny();
  139. serializedObject.ApplyModifiedProperties();
  140. }
  141. void ShowErrorsIfAny() {
  142. var errors = CollectErrors();
  143. ShowErrorIfPresent(errors, SoftMask.Errors.UnsupportedShaders, Labels.UnsupportedShaders, MessageType.Warning);
  144. ShowErrorIfPresent(errors, SoftMask.Errors.NestedMasks, Labels.NestedMasks, MessageType.Warning);
  145. ShowErrorIfPresent(errors, SoftMask.Errors.TightPackedSprite, Labels.TightPackedSprite, MessageType.Error);
  146. ShowErrorIfPresent(errors, SoftMask.Errors.AlphaSplitSprite, Labels.AlphaSplitSprite, MessageType.Error);
  147. ShowErrorIfPresent(errors, SoftMask.Errors.UnsupportedImageType, Labels.UnsupportedImageType, MessageType.Error);
  148. ShowErrorIfPresent(errors, SoftMask.Errors.UnreadableTexture, Labels.UnreadableTexture, MessageType.Error);
  149. ShowErrorIfPresent(errors, SoftMask.Errors.UnreadableRenderTexture, Labels.UnreadableRenderTexture, MessageType.Error);
  150. }
  151. SoftMask.Errors CollectErrors() {
  152. var result = SoftMask.Errors.NoError;
  153. foreach (var t in targets)
  154. result |= ((SoftMask)t).PollErrors();
  155. return result;
  156. }
  157. static void ShowErrorIfPresent(
  158. SoftMask.Errors actualErrors,
  159. SoftMask.Errors expectedError,
  160. string errorMessage,
  161. MessageType errorMessageType) {
  162. if ((actualErrors & expectedError) != 0)
  163. EditorGUILayout.HelpBox(errorMessage, errorMessageType);
  164. }
  165. public static class CustomEditors {
  166. public static bool ChannelWeights(GUIContent label, SerializedProperty weightsProp, bool customWeightsExpanded) {
  167. var rect = GUILayoutUtility.GetRect(GUIContent.none, KnownChannelStyle);
  168. if (customWeightsExpanded)
  169. rect.max = GUILayoutUtility.GetRect(GUIContent.none, CustomWeightsStyle).max;
  170. return ChannelWeights(rect, label, weightsProp, customWeightsExpanded);
  171. }
  172. public static void WithIndent(Action f) {
  173. ++EditorGUI.indentLevel;
  174. try {
  175. f();
  176. } finally {
  177. --EditorGUI.indentLevel;
  178. }
  179. }
  180. static GUIStyle KnownChannelStyle { get { return EditorStyles.popup; } }
  181. static GUIStyle CustomWeightsStyle { get { return EditorStyles.textField; } }
  182. static bool ChannelWeights(Rect rect, GUIContent label, SerializedProperty weightsProp, bool customWeightsExpanded) {
  183. var knownChannel =
  184. customWeightsExpanded
  185. ? KnownMaskChannel.Custom
  186. : KnownChannel(weightsProp.colorValue);
  187. label = EditorGUI.BeginProperty(rect, label, weightsProp);
  188. EditorGUI.BeginChangeCheck();
  189. knownChannel = (KnownMaskChannel)EditorGUI.EnumPopup(KnownChannelSubrect(rect), label, knownChannel);
  190. var weights = Weights(knownChannel, weightsProp.colorValue);
  191. if (customWeightsExpanded)
  192. WithIndent(() => {
  193. weights = ColorField(CustomWeightsSubrect(rect), Labels.ChannelWeights, weights);
  194. });
  195. if (EditorGUI.EndChangeCheck())
  196. weightsProp.colorValue = weights;
  197. if (Event.current.type != EventType.Layout)
  198. customWeightsExpanded = CustomChannelShouldBeExpandedFor(knownChannel);
  199. EditorGUI.EndProperty();
  200. return customWeightsExpanded;
  201. }
  202. static Rect KnownChannelSubrect(Rect rect) {
  203. var result = rect;
  204. result.height = HeightOf(KnownChannelStyle);
  205. return result;
  206. }
  207. static Rect CustomWeightsSubrect(Rect rect) {
  208. var result = rect;
  209. result.y += HeightOf(KnownChannelStyle) + Mathf.Max(KnownChannelStyle.margin.bottom, CustomWeightsStyle.margin.top);
  210. result.height = HeightOf(CustomWeightsStyle);
  211. return result;
  212. }
  213. static float HeightOf(GUIStyle style) { return style.CalcSize(GUIContent.none).y; }
  214. static Color ColorField(Rect rect, GUIContent label, Color color) {
  215. rect = EditorGUI.PrefixLabel(rect, label);
  216. color.r = ColorComponentField(HorizontalSlice(rect, 0, 4, 2), Labels.R, color.r);
  217. color.g = ColorComponentField(HorizontalSlice(rect, 1, 4, 2), Labels.G, color.g);
  218. color.b = ColorComponentField(HorizontalSlice(rect, 2, 4, 2), Labels.B, color.b);
  219. color.a = ColorComponentField(HorizontalSlice(rect, 3, 4, 2), Labels.A, color.a);
  220. return color;
  221. }
  222. static float ColorComponentField(Rect rect, GUIContent label, float value) {
  223. return WithZeroIndent(() => {
  224. var labelWidth = EditorStyles.label.CalcSize(label).x + 1;
  225. EditorGUI.LabelField(new Rect(rect) { width = labelWidth }, label);
  226. rect.width -= labelWidth;
  227. rect.x += labelWidth;
  228. return EditorGUI.FloatField(rect, value);
  229. });
  230. }
  231. static Rect HorizontalSlice(Rect whole, int part, int partCount, int spacing) {
  232. var result = new Rect(whole);
  233. result.width -= (partCount - 1) * spacing;
  234. result.width /= partCount;
  235. result.x += part * (result.width + spacing);
  236. return result;
  237. }
  238. static T WithZeroIndent<T>(Func<T> f) {
  239. var prev = EditorGUI.indentLevel;
  240. EditorGUI.indentLevel = 0;
  241. try {
  242. return f();
  243. } finally {
  244. EditorGUI.indentLevel = prev;
  245. }
  246. }
  247. enum KnownMaskChannel { Alpha, Red, Green, Blue, Gray, Custom }
  248. static KnownMaskChannel KnownChannel(Color weights) {
  249. if (weights == MaskChannel.alpha) return KnownMaskChannel.Alpha;
  250. else if (weights == MaskChannel.red) return KnownMaskChannel.Red;
  251. else if (weights == MaskChannel.green) return KnownMaskChannel.Green;
  252. else if (weights == MaskChannel.blue) return KnownMaskChannel.Blue;
  253. else if (weights == MaskChannel.gray) return KnownMaskChannel.Gray;
  254. else return KnownMaskChannel.Custom;
  255. }
  256. static Color Weights(KnownMaskChannel known, Color custom) {
  257. switch (known) {
  258. case KnownMaskChannel.Alpha: return MaskChannel.alpha;
  259. case KnownMaskChannel.Red: return MaskChannel.red;
  260. case KnownMaskChannel.Green: return MaskChannel.green;
  261. case KnownMaskChannel.Blue: return MaskChannel.blue;
  262. case KnownMaskChannel.Gray: return MaskChannel.gray;
  263. default: return custom;
  264. }
  265. }
  266. public static bool CustomChannelShouldBeExpandedFor(Color weights) {
  267. return CustomChannelShouldBeExpandedFor(KnownChannel(weights));
  268. }
  269. static bool CustomChannelShouldBeExpandedFor(KnownMaskChannel channel) {
  270. return channel == KnownMaskChannel.Custom;
  271. }
  272. }
  273. }
  274. }