BetterImage.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.ObjectModel;
  4. using System.Linq;
  5. using System.Text;
  6. using UnityEngine;
  7. using UnityEngine.Serialization;
  8. using UnityEngine.Sprites;
  9. using UnityEngine.UI;
  10. namespace TheraBytes.BetterUi
  11. {
  12. public enum ColorMode { Color, HorizontalGradient, VerticalGradient, }
  13. #if UNITY_2018_3_OR_NEWER
  14. [ExecuteAlways]
  15. #else
  16. [ExecuteInEditMode]
  17. #endif
  18. [HelpURL("https://documentation.therabytes.de/better-ui/BetterImage.html")]
  19. [AddComponentMenu("Better UI/Controls/Better Image", 30)]
  20. public class BetterImage : Image, IResolutionDependency, IImageAppearanceProvider
  21. {
  22. static readonly Vector2[] vertScratch = new Vector2[4];
  23. static readonly Vector2[] uvScratch = new Vector2[4];
  24. #region Nested Types
  25. [Serializable]
  26. public class SpriteSettings : IScreenConfigConnection
  27. {
  28. public Sprite Sprite;
  29. public ColorMode ColorMode;
  30. public Color PrimaryColor;
  31. public Color SecondaryColor;
  32. [SerializeField]
  33. string screenConfigName;
  34. public string ScreenConfigName { get { return screenConfigName; } set { screenConfigName = value; } }
  35. public SpriteSettings(Sprite sprite, ColorMode colorMode, Color primary, Color secondary)
  36. {
  37. this.Sprite = sprite;
  38. this.ColorMode = colorMode;
  39. this.PrimaryColor = primary;
  40. this.SecondaryColor = secondary;
  41. }
  42. }
  43. [Serializable]
  44. public class SpriteSettingsConfigCollection : SizeConfigCollection<SpriteSettings> { }
  45. #endregion
  46. #if !UNITY_2019_1_OR_NEWER
  47. float multipliedPixelsPerUnit => base.pixelsPerUnit;
  48. #endif
  49. public bool KeepBorderAspectRatio
  50. {
  51. get { return keepBorderAspectRatio; }
  52. set { keepBorderAspectRatio = value; SetVerticesDirty(); }
  53. }
  54. public Vector2SizeModifier SpriteBorderScale { get { return customBorderScales.GetCurrentItem(spriteBorderScaleFallback); } }
  55. public string MaterialType
  56. {
  57. get { return materialType; }
  58. set { ImageAppearanceProviderHelper.SetMaterialType(value, this, materialProperties, ref materialEffect, ref materialType); }
  59. }
  60. public MaterialEffect MaterialEffect
  61. {
  62. get { return materialEffect; }
  63. set { ImageAppearanceProviderHelper.SetMaterialEffect(value, this, materialProperties, ref materialEffect, ref materialType); }
  64. }
  65. public VertexMaterialData MaterialProperties { get { return materialProperties; } }
  66. public ColorMode ColoringMode
  67. {
  68. get { return colorMode; }
  69. set
  70. {
  71. Config.Set(value, (o) => colorMode = value, (o) => CurrentSpriteSettings.ColorMode = value);
  72. SetVerticesDirty();
  73. }
  74. }
  75. public Color SecondColor
  76. {
  77. get { return secondColor; }
  78. set
  79. {
  80. Config.Set(value, (o) => secondColor = value, (o) => CurrentSpriteSettings.SecondaryColor = value);
  81. SetVerticesDirty();
  82. }
  83. }
  84. public override Color color
  85. {
  86. get { return base.color; }
  87. set
  88. {
  89. Config.Set(value, (o) => base.color = value, (o) => CurrentSpriteSettings.PrimaryColor = value);
  90. }
  91. }
  92. public new Sprite sprite
  93. {
  94. get { return base.sprite; }
  95. set
  96. {
  97. Config.Set(value, (o) => base.sprite = value, (o) => CurrentSpriteSettings.Sprite = value);
  98. }
  99. }
  100. [SerializeField]
  101. ColorMode colorMode = ColorMode.Color;
  102. [SerializeField]
  103. Color secondColor = Color.white;
  104. [SerializeField]
  105. VertexMaterialData materialProperties = new VertexMaterialData();
  106. [SerializeField]
  107. string materialType;
  108. [SerializeField]
  109. MaterialEffect materialEffect;
  110. [SerializeField]
  111. float materialProperty1, materialProperty2, materialProperty3;
  112. [SerializeField]
  113. bool keepBorderAspectRatio;
  114. [FormerlySerializedAs("spriteBorderScale")]
  115. [SerializeField]
  116. Vector2SizeModifier spriteBorderScaleFallback =
  117. new Vector2SizeModifier(Vector2.one, Vector2.zero, 3 * Vector2.one);
  118. [SerializeField]
  119. Vector2SizeConfigCollection customBorderScales = new Vector2SizeConfigCollection();
  120. [SerializeField]
  121. SpriteSettings fallbackSpriteSettings;
  122. [SerializeField]
  123. SpriteSettingsConfigCollection customSpriteSettings = new SpriteSettingsConfigCollection();
  124. public SpriteSettings CurrentSpriteSettings
  125. {
  126. get
  127. {
  128. DoValidation();
  129. return customSpriteSettings.GetCurrentItem(fallbackSpriteSettings);
  130. }
  131. }
  132. Animator animator;
  133. private Sprite activeSprite => (overrideSprite != null) ? overrideSprite : sprite;
  134. protected override void OnEnable()
  135. {
  136. base.OnEnable();
  137. AssignSpriteSettings();
  138. animator = GetComponent<Animator>();
  139. if (MaterialProperties.FloatProperties != null)
  140. {
  141. if (MaterialProperties.FloatProperties.Length > 0)
  142. materialProperty1 = MaterialProperties.FloatProperties[0].Value;
  143. if (MaterialProperties.FloatProperties.Length > 1)
  144. materialProperty2 = MaterialProperties.FloatProperties[1].Value;
  145. if (MaterialProperties.FloatProperties.Length > 2)
  146. materialProperty3 = MaterialProperties.FloatProperties[2].Value;
  147. }
  148. }
  149. public float GetMaterialPropertyValue(int propertyIndex)
  150. {
  151. return ImageAppearanceProviderHelper.GetMaterialPropertyValue(propertyIndex,
  152. ref materialProperty1, ref materialProperty2, ref materialProperty3);
  153. }
  154. public void SetMaterialProperty(int propertyIndex, float value)
  155. {
  156. ImageAppearanceProviderHelper.SetMaterialProperty(propertyIndex, value, this, materialProperties,
  157. ref materialProperty1, ref materialProperty2, ref materialProperty3);
  158. }
  159. protected override void OnPopulateMesh(VertexHelper toFill)
  160. {
  161. if (animator != null
  162. && MaterialProperties.FloatProperties != null
  163. && (Application.isPlaying == animator.isActiveAndEnabled))
  164. {
  165. if (MaterialProperties.FloatProperties.Length > 0)
  166. MaterialProperties.FloatProperties[0].Value = materialProperty1;
  167. if (MaterialProperties.FloatProperties.Length > 1)
  168. MaterialProperties.FloatProperties[1].Value = materialProperty2;
  169. if (MaterialProperties.FloatProperties.Length > 2)
  170. MaterialProperties.FloatProperties[2].Value = materialProperty3;
  171. }
  172. if (this.activeSprite == null)
  173. {
  174. GenerateSimpleSprite(toFill, false);
  175. return;
  176. }
  177. switch (type)
  178. {
  179. case Type.Simple:
  180. GenerateSimpleSprite(toFill, base.preserveAspect);
  181. break;
  182. case Type.Sliced:
  183. GenerateSlicedSprite(toFill);
  184. break;
  185. case Type.Tiled:
  186. GenerateTiledSprite(toFill);
  187. break;
  188. case Type.Filled:
  189. default:
  190. base.OnPopulateMesh(toFill);
  191. break;
  192. }
  193. }
  194. #region Simple
  195. private void GenerateSimpleSprite(VertexHelper vh, bool preserveAspect)
  196. {
  197. Rect rect = GetDrawingRect(preserveAspect);
  198. Vector4 uv = (activeSprite == null)
  199. ? Vector4.zero
  200. : DataUtility.GetOuterUV(activeSprite);
  201. vh.Clear();
  202. AddQuad(vh, rect,
  203. rect.min, rect.max,
  204. colorMode, color, secondColor,
  205. new Vector2(uv.x, uv.y), new Vector2(uv.z, uv.w));
  206. }
  207. private Rect GetDrawingRect(bool shouldPreserveAspect)
  208. {
  209. Vector4 padding = (activeSprite == null) ? Vector4.zero : DataUtility.GetPadding(activeSprite);
  210. Vector2 spriteSize = (activeSprite == null) ? Vector2.zero : new Vector2(activeSprite.rect.width, activeSprite.rect.height);
  211. Rect rect = GetPixelAdjustedRect();
  212. if (activeSprite == null)
  213. return rect;
  214. float w = Mathf.RoundToInt(spriteSize.x);
  215. float h = Mathf.RoundToInt(spriteSize.y);
  216. Vector4 paddingFraction = new Vector4(
  217. padding.x / w,
  218. padding.y / h,
  219. (w - padding.z) / w,
  220. (h - padding.w) / h);
  221. if (shouldPreserveAspect && spriteSize.sqrMagnitude > 0f)
  222. {
  223. PreserveSpriteAspectRatio(ref rect, spriteSize);
  224. }
  225. var v = new Vector4(
  226. rect.x + rect.width * paddingFraction.x,
  227. rect.y + rect.height * paddingFraction.y,
  228. rect.x + rect.width * paddingFraction.z,
  229. rect.y + rect.height * paddingFraction.w);
  230. return new Rect(v.x, v.y, v.z - v.x, v.w - v.y);
  231. }
  232. private void PreserveSpriteAspectRatio(ref Rect rect, Vector2 spriteSize)
  233. {
  234. float spriteAspect = spriteSize.x / spriteSize.y;
  235. float rectAspect = rect.width / rect.height;
  236. if (spriteAspect > rectAspect)
  237. {
  238. float height = rect.height;
  239. rect.height = rect.width * (1f / spriteAspect);
  240. rect.y += (height - rect.height) * base.rectTransform.pivot.y;
  241. }
  242. else
  243. {
  244. float width = rect.width;
  245. rect.width = rect.height * spriteAspect;
  246. rect.x += (width - rect.width) * base.rectTransform.pivot.x;
  247. }
  248. }
  249. #endregion
  250. #region Sliced
  251. void GenerateSlicedSprite(VertexHelper toFill)
  252. {
  253. if (!hasBorder)
  254. {
  255. base.OnPopulateMesh(toFill);
  256. return;
  257. }
  258. Vector4 outer, inner, padding, border;
  259. if (activeSprite != null)
  260. {
  261. outer = DataUtility.GetOuterUV(activeSprite);
  262. inner = DataUtility.GetInnerUV(activeSprite);
  263. padding = DataUtility.GetPadding(activeSprite);
  264. border = activeSprite.border;
  265. }
  266. else
  267. {
  268. outer = Vector4.zero;
  269. inner = Vector4.zero;
  270. padding = Vector4.zero;
  271. border = Vector4.zero;
  272. }
  273. Vector2 scale = SpriteBorderScale.CalculateSize(this) / multipliedPixelsPerUnit;
  274. Rect rect = GetPixelAdjustedRect();
  275. border = new Vector4(
  276. scale.x * border.x,
  277. scale.y * border.y,
  278. scale.x * border.z,
  279. scale.y * border.w);
  280. border = GetAdjustedBorders(border, rect, KeepBorderAspectRatio,
  281. new Vector2(
  282. activeSprite.rect.width * scale.x,
  283. activeSprite.rect.height * scale.y) );
  284. if ((border.x + border.z) > rect.width)
  285. {
  286. float s = rect.width / (border.x + border.z);
  287. border.x *= s;
  288. border.z *= s;
  289. }
  290. if (border.y + border.w > rect.height)
  291. {
  292. float s = rect.height / (border.y + border.w);
  293. border.y *= s;
  294. border.w *= s;
  295. }
  296. padding = padding / multipliedPixelsPerUnit;
  297. vertScratch[0] = new Vector2(padding.x, padding.y);
  298. vertScratch[3] = new Vector2(rect.width - padding.z, rect.height - padding.w);
  299. vertScratch[1].x = border.x;
  300. vertScratch[1].y = border.y;
  301. vertScratch[2].x = rect.width - border.z;
  302. vertScratch[2].y = rect.height - border.w;
  303. for (int i = 0; i < 4; i++)
  304. {
  305. vertScratch[i].x += rect.x;
  306. vertScratch[i].y += rect.y;
  307. }
  308. uvScratch[0] = new Vector2(outer.x, outer.y);
  309. uvScratch[1] = new Vector2(inner.x, inner.y);
  310. uvScratch[2] = new Vector2(inner.z, inner.w);
  311. uvScratch[3] = new Vector2(outer.z, outer.w);
  312. toFill.Clear();
  313. for (int x = 0; x < 3; x++)
  314. {
  315. int xIdx = x + 1;
  316. for (int y = 0; y < 3; y++)
  317. {
  318. if (this.fillCenter || x != 1 || y != 1)
  319. {
  320. int yIdx = y + 1;
  321. AddQuad(toFill,
  322. posMin: new Vector2(vertScratch[x].x, vertScratch[y].y),
  323. posMax: new Vector2(vertScratch[xIdx].x, vertScratch[yIdx].y),
  324. bounds: rect,
  325. mode: colorMode, colorA: color, colorB: secondColor,
  326. uvMin: new Vector2(uvScratch[x].x, uvScratch[y].y),
  327. uvMax: new Vector2(uvScratch[xIdx].x, uvScratch[yIdx].y));
  328. }
  329. }
  330. }
  331. }
  332. #endregion
  333. #region Tiled
  334. private void GenerateTiledSprite(VertexHelper toFill)
  335. {
  336. Vector4 outerUV, innerUV, border;
  337. Vector2 spriteSize;
  338. if (activeSprite == null)
  339. {
  340. outerUV = Vector4.zero;
  341. innerUV = Vector4.zero;
  342. border = Vector4.zero;
  343. spriteSize = Vector2.one * 100f;
  344. }
  345. else
  346. {
  347. outerUV = DataUtility.GetOuterUV(activeSprite);
  348. innerUV = DataUtility.GetInnerUV(activeSprite);
  349. border = activeSprite.border;
  350. spriteSize = activeSprite.rect.size;
  351. }
  352. Rect rect = base.GetPixelAdjustedRect();
  353. float tileWidth = (spriteSize.x - border.x - border.z) / multipliedPixelsPerUnit;
  354. float tileHeight = (spriteSize.y - border.y - border.w) / multipliedPixelsPerUnit;
  355. border = this.GetAdjustedBorders(border / multipliedPixelsPerUnit, rect, false,
  356. new Vector2(
  357. activeSprite.textureRect.width,
  358. activeSprite.textureRect.height));
  359. Vector2 scale = SpriteBorderScale.CalculateSize(this);
  360. tileWidth *= scale.x;
  361. tileHeight *= scale.y;
  362. Vector2 uvMin = new Vector2(innerUV.x, innerUV.y);
  363. Vector2 uvMax = new Vector2(innerUV.z, innerUV.w);
  364. UIVertex.simpleVert.color = this.color;
  365. // Min to max max range for tiled region in coordinates relative to lower left corner.
  366. float xMin = scale.x * border.x;
  367. float xMax = rect.width - (scale.x * border.z);
  368. float yMin = scale.y * border.y;
  369. float yMax = rect.height - (scale.y * border.w);
  370. toFill.Clear();
  371. Vector2 uvMax2 = uvMax;
  372. Vector2 pos = rect.position;
  373. if (tileWidth <= 0f)
  374. {
  375. tileWidth = xMax - xMin;
  376. }
  377. if (tileHeight <= 0f)
  378. {
  379. tileHeight = yMax - yMin;
  380. }
  381. if (this.fillCenter)
  382. {
  383. for (float y1 = yMin; y1 < yMax; y1 = y1 + tileHeight)
  384. {
  385. float y2 = y1 + tileHeight;
  386. if (y2 > yMax)
  387. {
  388. uvMax2.y = uvMin.y + (uvMax.y - uvMin.y) * (yMax - y1) / (y2 - y1);
  389. y2 = yMax;
  390. }
  391. uvMax2.x = uvMax.x;
  392. for (float x1 = xMin; x1 < xMax; x1 = x1 + tileWidth)
  393. {
  394. float x2 = x1 + tileWidth;
  395. if (x2 > xMax)
  396. {
  397. uvMax2.x = uvMin.x + (uvMax.x - uvMin.x) * (xMax - x1) / (x2 - x1);
  398. x2 = xMax;
  399. }
  400. AddQuad(toFill, rect,
  401. new Vector2(x1, y1) + pos,
  402. new Vector2(x2, y2) + pos,
  403. colorMode, color, secondColor,
  404. uvMin, uvMax2);
  405. }
  406. }
  407. }
  408. if (this.hasBorder)
  409. {
  410. uvMax2 = uvMax;
  411. for (float y1 = yMin; y1 < yMax; y1 = y1 + tileHeight)
  412. {
  413. float y2 = y1 + tileHeight;
  414. if (y2 > yMax)
  415. {
  416. uvMax2.y = uvMin.y + (uvMax.y - uvMin.y) * (yMax - y1) / (y2 - y1);
  417. y2 = yMax;
  418. }
  419. AddQuad(toFill, rect,
  420. new Vector2(0f, y1) + pos,
  421. new Vector2(xMin, y2) + pos,
  422. colorMode, color, secondColor,
  423. new Vector2(outerUV.x, uvMin.y), new Vector2(uvMin.x, uvMax2.y));
  424. AddQuad(toFill, rect,
  425. new Vector2(xMax, y1) + pos,
  426. new Vector2(rect.width, y2) + pos,
  427. colorMode, color, secondColor,
  428. new Vector2(uvMax.x, uvMin.y), new Vector2(outerUV.z, uvMax2.y));
  429. }
  430. uvMax2 = uvMax;
  431. for (float x1 = xMin; x1 < xMax; x1 = x1 + tileWidth)
  432. {
  433. float x2 = x1 + tileWidth;
  434. if (x2 > xMax)
  435. {
  436. uvMax2.x = uvMin.x + (uvMax.x - uvMin.x) * (xMax - x1) / (x2 - x1);
  437. x2 = xMax;
  438. }
  439. AddQuad(toFill, rect,
  440. new Vector2(x1, 0f) + pos,
  441. new Vector2(x2, yMin) + pos,
  442. colorMode, color, secondColor,
  443. new Vector2(uvMin.x, outerUV.y), new Vector2(uvMax2.x, uvMin.y));
  444. AddQuad(toFill, rect,
  445. new Vector2(x1, yMax) + pos,
  446. new Vector2(x2, rect.height) + pos,
  447. colorMode, color, secondColor,
  448. new Vector2(uvMin.x, uvMax.y), new Vector2(uvMax2.x, outerUV.w));
  449. }
  450. AddQuad(toFill, rect,
  451. new Vector2(0f, 0f) + pos,
  452. new Vector2(xMin, yMin) + pos,
  453. colorMode, color, secondColor,
  454. new Vector2(outerUV.x, outerUV.y), new Vector2(uvMin.x, uvMin.y));
  455. AddQuad(toFill, rect,
  456. new Vector2(xMax, 0f) + pos,
  457. new Vector2(rect.width, yMin) + pos,
  458. colorMode, color, secondColor,
  459. new Vector2(uvMax.x, outerUV.y), new Vector2(outerUV.z, uvMin.y));
  460. AddQuad(toFill, rect,
  461. new Vector2(0f, yMax) + pos,
  462. new Vector2(xMin, rect.height) + pos,
  463. colorMode, color, secondColor,
  464. new Vector2(outerUV.x, uvMax.y), new Vector2(uvMin.x, outerUV.w));
  465. AddQuad(toFill, rect,
  466. new Vector2(xMax, yMax) + pos,
  467. new Vector2(rect.width, rect.height) + pos,
  468. colorMode, color, secondColor,
  469. new Vector2(uvMax.x, uvMax.y), new Vector2(outerUV.z, outerUV.w));
  470. }
  471. }
  472. #endregion
  473. private void AddQuad(
  474. VertexHelper vertexHelper, Rect bounds,
  475. Vector2 posMin, Vector2 posMax,
  476. ColorMode mode, Color colorA, Color colorB,
  477. Vector2 uvMin, Vector2 uvMax)
  478. {
  479. ImageAppearanceProviderHelper.AddQuad(vertexHelper, bounds, posMin, posMax, mode, colorA, colorB, uvMin, uvMax, materialProperties);
  480. }
  481. Vector4 GetAdjustedBorders(Vector4 border, Rect rect, bool keepAspect, Vector2 texSize)
  482. {
  483. float scale = 1;
  484. for (int axis = 0; axis <= 1; axis++)
  485. {
  486. // If the rect is smaller than the combined borders, then there's not room for the borders at their normal size.
  487. // In order to avoid artefacts with overlapping borders, we scale the borders down to fit.
  488. float combinedBorders = border[axis] + border[axis + 2];
  489. if (rect.size[axis] < combinedBorders)// && combinedBorders != 0)
  490. {
  491. if(keepAspect)
  492. {
  493. scale = Mathf.Min(scale, rect.size[axis] / combinedBorders);
  494. }
  495. else
  496. {
  497. float borderScaleRatio = rect.size[axis] / combinedBorders;
  498. border[axis] *= borderScaleRatio;
  499. border[axis + 2] *= borderScaleRatio;
  500. }
  501. }
  502. else if (combinedBorders == 0 && keepAspect)
  503. {
  504. int o = (axis + 1) % 2;
  505. combinedBorders = border[o] + border[o + 2];
  506. scale = rect.size[axis] / texSize[axis];
  507. if(scale * combinedBorders > rect.size[o])
  508. {
  509. scale = rect.size[o] / combinedBorders;
  510. }
  511. }
  512. }
  513. if (keepAspect)
  514. {
  515. border = scale * border;
  516. }
  517. return border;
  518. }
  519. public void OnResolutionChanged()
  520. {
  521. SetVerticesDirty();
  522. AssignSpriteSettings();
  523. }
  524. private void AssignSpriteSettings()
  525. {
  526. var settings = CurrentSpriteSettings;
  527. this.sprite = settings.Sprite;
  528. this.colorMode = settings.ColorMode;
  529. this.color = settings.PrimaryColor;
  530. this.secondColor = settings.SecondaryColor;
  531. }
  532. #if UNITY_EDITOR
  533. protected override void OnValidate()
  534. {
  535. base.OnValidate();
  536. DoValidation();
  537. AssignSpriteSettings();
  538. }
  539. #endif
  540. private void DoValidation()
  541. {
  542. bool isUnInitialized = fallbackSpriteSettings == null
  543. || (fallbackSpriteSettings.Sprite == null
  544. && fallbackSpriteSettings.ColorMode == ColorMode.Color
  545. && fallbackSpriteSettings.PrimaryColor == new Color());
  546. if (isUnInitialized)
  547. {
  548. fallbackSpriteSettings = new SpriteSettings(this.sprite, this.colorMode, this.color, this.secondColor);
  549. }
  550. }
  551. }
  552. }