源战役客户端
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

600 строки
21 KiB

4 недель назад
  1. using UnityEngine;
  2. using UnityEditor;
  3. using System.Collections;
  4. [System.Serializable]
  5. public class ZoomArea
  6. {
  7. // Global state
  8. private static Vector2 m_MouseDownPosition = new Vector2(-1000000, -1000000); // in transformed space
  9. private static int zoomableAreaHash = "ZoomableArea".GetHashCode();
  10. // Range lock settings
  11. [SerializeField]
  12. private bool m_HRangeLocked;
  13. [SerializeField]
  14. private bool m_VRangeLocked;
  15. public bool hRangeLocked { get { return m_HRangeLocked; } set { m_HRangeLocked = value; } }
  16. public bool vRangeLocked { get { return m_VRangeLocked; } set { m_VRangeLocked = value; } }
  17. [SerializeField]
  18. private float m_HBaseRangeMin = 0;
  19. [SerializeField]
  20. private float m_HBaseRangeMax = 1;
  21. [SerializeField]
  22. private float m_VBaseRangeMin = 0;
  23. [SerializeField]
  24. private float m_VBaseRangeMax = 1;
  25. public float hBaseRangeMin { get { return m_HBaseRangeMin; } set { m_HBaseRangeMin = value; } }
  26. public float hBaseRangeMax { get { return m_HBaseRangeMax; } set { m_HBaseRangeMax = value; } }
  27. public float vBaseRangeMin { get { return m_VBaseRangeMin; } set { m_VBaseRangeMin = value; } }
  28. public float vBaseRangeMax { get { return m_VBaseRangeMax; } set { m_VBaseRangeMax = value; } }
  29. [SerializeField]
  30. private bool m_HAllowExceedBaseRangeMin = true;
  31. [SerializeField]
  32. private bool m_HAllowExceedBaseRangeMax = true;
  33. [SerializeField]
  34. private bool m_VAllowExceedBaseRangeMin = true;
  35. [SerializeField]
  36. private bool m_VAllowExceedBaseRangeMax = true;
  37. public bool hAllowExceedBaseRangeMin { get { return m_HAllowExceedBaseRangeMin; } set { m_HAllowExceedBaseRangeMin = value; } }
  38. public bool hAllowExceedBaseRangeMax { get { return m_HAllowExceedBaseRangeMax; } set { m_HAllowExceedBaseRangeMax = value; } }
  39. public bool vAllowExceedBaseRangeMin { get { return m_VAllowExceedBaseRangeMin; } set { m_VAllowExceedBaseRangeMin = value; } }
  40. public bool vAllowExceedBaseRangeMax { get { return m_VAllowExceedBaseRangeMax; } set { m_VAllowExceedBaseRangeMax = value; } }
  41. public float hRangeMin
  42. {
  43. get { return (hAllowExceedBaseRangeMin ? Mathf.NegativeInfinity : hBaseRangeMin); }
  44. set { SetAllowExceed(ref m_HBaseRangeMin, ref m_HAllowExceedBaseRangeMin, value); }
  45. }
  46. public float hRangeMax
  47. {
  48. get { return (hAllowExceedBaseRangeMax ? Mathf.Infinity : hBaseRangeMax); }
  49. set { SetAllowExceed(ref m_HBaseRangeMax, ref m_HAllowExceedBaseRangeMax, value); }
  50. }
  51. public float vRangeMin
  52. {
  53. get { return (vAllowExceedBaseRangeMin ? Mathf.NegativeInfinity : vBaseRangeMin); }
  54. set { SetAllowExceed(ref m_VBaseRangeMin, ref m_VAllowExceedBaseRangeMin, value); }
  55. }
  56. public float vRangeMax
  57. {
  58. get { return (vAllowExceedBaseRangeMax ? Mathf.Infinity : vBaseRangeMax); }
  59. set { SetAllowExceed(ref m_VBaseRangeMax, ref m_VAllowExceedBaseRangeMax, value); }
  60. }
  61. private void SetAllowExceed(ref float rangeEnd, ref bool allowExceed, float value)
  62. {
  63. if (value == Mathf.NegativeInfinity || value == Mathf.Infinity)
  64. {
  65. rangeEnd = (value == Mathf.NegativeInfinity ? 0 : 1);
  66. allowExceed = true;
  67. }
  68. else
  69. {
  70. rangeEnd = value;
  71. allowExceed = false;
  72. }
  73. }
  74. private float m_HScaleMin = 0.001f;
  75. private float m_HScaleMax = 100000.0f;
  76. private float m_VScaleMin = 0.001f;
  77. private float m_VScaleMax = 100000.0f;
  78. // Window resize settings
  79. [SerializeField]
  80. private bool m_ScaleWithWindow = false;
  81. public bool scaleWithWindow { get { return m_ScaleWithWindow; } set { m_ScaleWithWindow = value; } }
  82. // Slider settings
  83. [SerializeField]
  84. private bool m_HSlider = true;
  85. [SerializeField]
  86. private bool m_VSlider = true;
  87. public bool hSlider { get { return m_HSlider; } set { Rect r = rect; m_HSlider = value; rect = r; } }
  88. public bool vSlider { get { return m_VSlider; } set { Rect r = rect; m_VSlider = value; rect = r; } }
  89. [SerializeField]
  90. private bool m_IgnoreScrollWheelUntilClicked = false;
  91. public bool ignoreScrollWheelUntilClicked { get { return m_IgnoreScrollWheelUntilClicked; } set { m_IgnoreScrollWheelUntilClicked = value; } }
  92. public bool m_UniformScale;
  93. public bool uniformScale { get { return m_UniformScale; } set { m_UniformScale = value; } }
  94. // View and drawing settings
  95. [SerializeField]
  96. private Rect m_DrawArea = new Rect(0, 0, 100, 100);
  97. internal void SetDrawRectHack(Rect r) { m_DrawArea = r; }
  98. [SerializeField]
  99. internal Vector2 m_Scale = new Vector2(1, -1);
  100. [SerializeField]
  101. internal Vector2 m_Translation = new Vector2(0, 0);
  102. [SerializeField]
  103. private float m_MarginLeft, m_MarginRight, m_MarginTop, m_MarginBottom;
  104. [SerializeField]
  105. private Rect m_LastShownAreaInsideMargins = new Rect(0, 0, 100, 100);
  106. public Vector2 scale { get { return m_Scale; } }
  107. public float margin { set { m_MarginLeft = m_MarginRight = m_MarginTop = m_MarginBottom = value; } }
  108. public float leftmargin { get { return m_MarginLeft; } set { m_MarginLeft = value; } }
  109. public float rightmargin { get { return m_MarginRight; } set { m_MarginRight = value; } }
  110. public float topmargin { get { return m_MarginTop; } set { m_MarginTop = value; } }
  111. public float bottommargin { get { return m_MarginBottom; } set { m_MarginBottom = value; } }
  112. [SerializeField]
  113. bool m_MinimalGUI;
  114. [System.Serializable]
  115. public class Styles
  116. {
  117. public GUIStyle background = "AnimationCurveEditorBackground";
  118. public GUIStyle horizontalScrollbar;
  119. public GUIStyle horizontalMinMaxScrollbarThumb;
  120. public GUIStyle horizontalScrollbarLeftButton;
  121. public GUIStyle horizontalScrollbarRightButton;
  122. public GUIStyle verticalScrollbar;
  123. public GUIStyle verticalMinMaxScrollbarThumb;
  124. public GUIStyle verticalScrollbarUpButton;
  125. public GUIStyle verticalScrollbarDownButton;
  126. public float sliderWidth;
  127. public float visualSliderWidth;
  128. public Styles(bool minimalGUI)
  129. {
  130. if (minimalGUI)
  131. {
  132. visualSliderWidth = 0;
  133. sliderWidth = 15;
  134. }
  135. else
  136. {
  137. visualSliderWidth = 15;
  138. sliderWidth = 15;
  139. }
  140. }
  141. public void InitGUIStyles(bool minimalGUI)
  142. {
  143. if (minimalGUI)
  144. {
  145. horizontalMinMaxScrollbarThumb = "MiniMinMaxSliderHorizontal";
  146. horizontalScrollbarLeftButton = GUIStyle.none;
  147. horizontalScrollbarRightButton = GUIStyle.none;
  148. horizontalScrollbar = GUIStyle.none;
  149. verticalMinMaxScrollbarThumb = "MiniMinMaxSlidervertical";
  150. verticalScrollbarUpButton = GUIStyle.none;
  151. verticalScrollbarDownButton = GUIStyle.none;
  152. verticalScrollbar = GUIStyle.none;
  153. }
  154. else
  155. {
  156. horizontalMinMaxScrollbarThumb = "horizontalMinMaxScrollbarThumb";
  157. horizontalScrollbarLeftButton = "horizontalScrollbarLeftbutton";
  158. horizontalScrollbarRightButton = "horizontalScrollbarRightbutton";
  159. horizontalScrollbar = GUI.skin.horizontalScrollbar;
  160. verticalMinMaxScrollbarThumb = "verticalMinMaxScrollbarThumb";
  161. verticalScrollbarUpButton = "verticalScrollbarUpbutton";
  162. verticalScrollbarDownButton = "verticalScrollbarDownbutton";
  163. verticalScrollbar = GUI.skin.verticalScrollbar;
  164. }
  165. }
  166. }
  167. private Styles m_Styles;
  168. private Styles styles
  169. {
  170. get
  171. {
  172. if (m_Styles == null)
  173. m_Styles = new Styles(m_MinimalGUI);
  174. return m_Styles;
  175. }
  176. }
  177. public Rect rect
  178. {
  179. get { return new Rect(drawRect.x, drawRect.y, drawRect.width + (m_VSlider ? styles.visualSliderWidth : 0), drawRect.height + (m_HSlider ? styles.visualSliderWidth : 0)); }
  180. set
  181. {
  182. Rect newDrawArea = new Rect(value.x, value.y, value.width - (m_VSlider ? styles.visualSliderWidth : 0), value.height - (m_HSlider ? styles.visualSliderWidth : 0));
  183. if (newDrawArea != m_DrawArea)
  184. {
  185. if (m_ScaleWithWindow)
  186. {
  187. m_DrawArea = newDrawArea;
  188. shownAreaInsideMargins = m_LastShownAreaInsideMargins;
  189. }
  190. else
  191. {
  192. m_Translation += new Vector2((newDrawArea.width - m_DrawArea.width) / 2, (newDrawArea.height - m_DrawArea.height) / 2);
  193. m_DrawArea = newDrawArea;
  194. }
  195. }
  196. EnforceScaleAndRange();
  197. }
  198. }
  199. public Rect drawRect { get { return m_DrawArea; } }
  200. public void SetShownHRangeInsideMargins(float min, float max)
  201. {
  202. m_Scale.x = (drawRect.width - leftmargin - rightmargin) / (max - min);
  203. m_Translation.x = -min * m_Scale.x + leftmargin;
  204. EnforceScaleAndRange();
  205. }
  206. public void SetShownHRange(float min, float max)
  207. {
  208. m_Scale.x = drawRect.width / (max - min);
  209. m_Translation.x = -min * m_Scale.x;
  210. EnforceScaleAndRange();
  211. }
  212. public void SetShownVRangeInsideMargins(float min, float max)
  213. {
  214. m_Scale.y = -(drawRect.height - topmargin - bottommargin) / (max - min);
  215. m_Translation.y = drawRect.height - min * m_Scale.y - topmargin;
  216. EnforceScaleAndRange();
  217. }
  218. public void SetShownVRange(float min, float max)
  219. {
  220. m_Scale.y = -drawRect.height / (max - min);
  221. m_Translation.y = drawRect.height - min * m_Scale.y;
  222. EnforceScaleAndRange();
  223. }
  224. // ShownArea is in curve space
  225. public Rect shownArea
  226. {
  227. set
  228. {
  229. m_Scale.x = drawRect.width / value.width;
  230. m_Scale.y = -drawRect.height / value.height;
  231. m_Translation.x = -value.x * m_Scale.x;
  232. m_Translation.y = drawRect.height - value.y * m_Scale.y;
  233. EnforceScaleAndRange();
  234. }
  235. get
  236. {
  237. return new Rect(
  238. -m_Translation.x / m_Scale.x,
  239. -(m_Translation.y - drawRect.height) / m_Scale.y,
  240. drawRect.width / m_Scale.x,
  241. drawRect.height / -m_Scale.y
  242. );
  243. }
  244. }
  245. public Rect shownAreaInsideMargins
  246. {
  247. set
  248. {
  249. shownAreaInsideMarginsInternal = value;
  250. EnforceScaleAndRange();
  251. }
  252. get
  253. {
  254. return shownAreaInsideMarginsInternal;
  255. }
  256. }
  257. private Rect shownAreaInsideMarginsInternal
  258. {
  259. set
  260. {
  261. m_Scale.x = (drawRect.width - leftmargin - rightmargin) / value.width;
  262. m_Scale.y = -(drawRect.height - topmargin - bottommargin) / value.height;
  263. m_Translation.x = -value.x * m_Scale.x + leftmargin;
  264. m_Translation.y = drawRect.height - value.y * m_Scale.y - topmargin;
  265. }
  266. get
  267. {
  268. float leftmarginRel = leftmargin / m_Scale.x;
  269. float rightmarginRel = rightmargin / m_Scale.x;
  270. float topmarginRel = topmargin / m_Scale.y;
  271. float bottommarginRel = bottommargin / m_Scale.y;
  272. Rect area = shownArea;
  273. area.x += leftmarginRel;
  274. area.y -= topmarginRel;
  275. area.width -= leftmarginRel + rightmarginRel;
  276. area.height += topmarginRel + bottommarginRel;
  277. return area;
  278. }
  279. }
  280. public virtual Bounds drawingBounds
  281. {
  282. get
  283. {
  284. return new Bounds(
  285. new Vector3((hBaseRangeMin + hBaseRangeMax) * 0.5f, (vBaseRangeMin + vBaseRangeMax) * 0.5f, 0),
  286. new Vector3(hBaseRangeMax - hBaseRangeMin, vBaseRangeMax - vBaseRangeMin, 1)
  287. );
  288. }
  289. }
  290. // Utility transform functions
  291. public Matrix4x4 drawingToViewMatrix
  292. {
  293. get
  294. {
  295. return Matrix4x4.TRS(m_Translation, Quaternion.identity, new Vector3(m_Scale.x, m_Scale.y, 1));
  296. }
  297. }
  298. public Vector2 DrawingToViewTransformPoint(Vector2 lhs)
  299. { return new Vector2(lhs.x * m_Scale.x + m_Translation.x, lhs.y * m_Scale.y + m_Translation.y); }
  300. public Vector3 DrawingToViewTransformPoint(Vector3 lhs)
  301. { return new Vector3(lhs.x * m_Scale.x + m_Translation.x, lhs.y * m_Scale.y + m_Translation.y, 0); }
  302. public Vector2 ViewToDrawingTransformPoint(Vector2 lhs)
  303. { return new Vector2((lhs.x - m_Translation.x) / m_Scale.x, (lhs.y - m_Translation.y) / m_Scale.y); }
  304. public Vector3 ViewToDrawingTransformPoint(Vector3 lhs)
  305. { return new Vector3((lhs.x - m_Translation.x) / m_Scale.x, (lhs.y - m_Translation.y) / m_Scale.y, 0); }
  306. public Vector2 DrawingToViewTransformVector(Vector2 lhs)
  307. { return new Vector2(lhs.x * m_Scale.x, lhs.y * m_Scale.y); }
  308. public Vector3 DrawingToViewTransformVector(Vector3 lhs)
  309. { return new Vector3(lhs.x * m_Scale.x, lhs.y * m_Scale.y, 0); }
  310. public Vector2 ViewToDrawingTransformVector(Vector2 lhs)
  311. { return new Vector2(lhs.x / m_Scale.x, lhs.y / m_Scale.y); }
  312. public Vector3 ViewToDrawingTransformVector(Vector3 lhs)
  313. { return new Vector3(lhs.x / m_Scale.x, lhs.y / m_Scale.y, 0); }
  314. public Vector2 mousePositionInDrawing
  315. {
  316. get { return ViewToDrawingTransformPoint(Event.current.mousePosition); }
  317. }
  318. public Vector2 NormalizeInViewSpace(Vector2 vec)
  319. {
  320. vec = Vector2.Scale(vec, m_Scale);
  321. vec /= vec.magnitude;
  322. return Vector2.Scale(vec, new Vector2(1 / m_Scale.x, 1 / m_Scale.y));
  323. }
  324. // Utility mouse event functions
  325. private bool IsZoomEvent()
  326. {
  327. return (
  328. (Event.current.button == 1 && Event.current.alt) // right+alt drag
  329. //|| (Event.current.button == 0 && Event.current.command) // left+commend drag
  330. //|| (Event.current.button == 2 && Event.current.command) // middle+command drag
  331. );
  332. }
  333. private bool IsPanEvent()
  334. {
  335. return (
  336. (Event.current.button == 0 && Event.current.alt) // left+alt drag
  337. || (Event.current.button == 2 && !Event.current.command) // middle drag
  338. );
  339. }
  340. public ZoomArea()
  341. {
  342. m_MinimalGUI = false;
  343. }
  344. public ZoomArea(bool minimalGUI)
  345. {
  346. m_MinimalGUI = minimalGUI;
  347. }
  348. public void BeginViewGUI()
  349. {
  350. if (styles.horizontalScrollbar == null)
  351. styles.InitGUIStyles(m_MinimalGUI);
  352. GUILayout.BeginArea(m_DrawArea, styles.background);
  353. HandleZoomAndPanEvents(m_DrawArea);
  354. GUILayout.EndArea();
  355. }
  356. public void HandleZoomAndPanEvents(Rect area)
  357. {
  358. area.x = 0;
  359. area.y = 0;
  360. int id = GUIUtility.GetControlID(zoomableAreaHash, FocusType.Passive, area);
  361. switch (Event.current.GetTypeForControl(id))
  362. {
  363. case EventType.MouseDown:
  364. if (area.Contains(Event.current.mousePosition))
  365. {
  366. // Catch keyboard control when clicked inside zoomable area
  367. // (used to restrict scrollwheel)
  368. GUIUtility.keyboardControl = id;
  369. if (IsZoomEvent() || IsPanEvent())
  370. {
  371. GUIUtility.hotControl = id;
  372. m_MouseDownPosition = mousePositionInDrawing;
  373. Event.current.Use();
  374. }
  375. }
  376. break;
  377. case EventType.MouseUp:
  378. //Debug.Log("mouse-up!");
  379. if (GUIUtility.hotControl == id)
  380. {
  381. GUIUtility.hotControl = 0;
  382. // If we got the mousedown, the mouseup is ours as well
  383. // (no matter if the click was in the area or not)
  384. m_MouseDownPosition = new Vector2(-1000000, -1000000);
  385. //Event.current.Use();
  386. }
  387. break;
  388. case EventType.MouseDrag:
  389. if (GUIUtility.hotControl != id) break;
  390. if (IsZoomEvent())
  391. {
  392. // Zoom in around mouse down position
  393. Zoom(m_MouseDownPosition, false);
  394. Event.current.Use();
  395. }
  396. else if (IsPanEvent())
  397. {
  398. // Pan view
  399. Pan();
  400. Event.current.Use();
  401. }
  402. break;
  403. case EventType.ScrollWheel:
  404. if (!area.Contains(Event.current.mousePosition))
  405. break;
  406. if (m_IgnoreScrollWheelUntilClicked && GUIUtility.keyboardControl != id)
  407. break;
  408. // Zoom in around cursor position
  409. Zoom(mousePositionInDrawing, true);
  410. Event.current.Use();
  411. break;
  412. }
  413. }
  414. public void EndViewGUI()
  415. {
  416. }
  417. private void Pan()
  418. {
  419. if (!m_HRangeLocked)
  420. m_Translation.x += Event.current.delta.x;
  421. if (!m_VRangeLocked)
  422. m_Translation.y += Event.current.delta.y;
  423. EnforceScaleAndRange();
  424. }
  425. private void Zoom(Vector2 zoomAround, bool scrollwhell)
  426. {
  427. // Get delta (from scroll wheel or mouse pad)
  428. // Add x and y delta to cover all cases
  429. // (scrool view has only y or only x when shift is pressed,
  430. // while mouse pad has both x and y at all times)
  431. float delta = Event.current.delta.x + Event.current.delta.y;
  432. if (scrollwhell)
  433. delta = -delta;
  434. // Scale multiplier. Don't allow scale of zero or below!
  435. float scale = Mathf.Max(0.01F, 1 + delta * 0.01F);
  436. if (!m_HRangeLocked && !Event.current.shift)
  437. {
  438. // Offset to make zoom centered around cursor position
  439. m_Translation.x -= zoomAround.x * (scale - 1) * m_Scale.x;
  440. // Apply zooming
  441. m_Scale.x *= scale;
  442. }
  443. if (!m_VRangeLocked && !EditorGUI.actionKey)
  444. {
  445. // Offset to make zoom centered around cursor position
  446. m_Translation.y -= zoomAround.y * (scale - 1) * m_Scale.y;
  447. // Apply zooming
  448. m_Scale.y *= scale;
  449. }
  450. EnforceScaleAndRange();
  451. }
  452. public void EnforceScaleAndRange()
  453. {
  454. float hScaleMin = m_HScaleMin;
  455. float vScaleMin = m_VScaleMin;
  456. float hScaleMax = m_HScaleMax;
  457. float vScaleMax = m_VScaleMax;
  458. if (hRangeMax != Mathf.Infinity && hRangeMin != Mathf.NegativeInfinity)
  459. hScaleMax = Mathf.Min(m_HScaleMax, hRangeMax - hRangeMin);
  460. if (vRangeMax != Mathf.Infinity && vRangeMin != Mathf.NegativeInfinity)
  461. vScaleMax = Mathf.Min(m_VScaleMax, vRangeMax - vRangeMin);
  462. Rect oldArea = m_LastShownAreaInsideMargins;
  463. Rect newArea = shownAreaInsideMargins;
  464. if (newArea == oldArea)
  465. return;
  466. float epsilon = 0.00001f;
  467. if (newArea.width < oldArea.width - epsilon)
  468. {
  469. float xLerp = Mathf.InverseLerp(oldArea.width, newArea.width, hScaleMin);
  470. newArea = new Rect(
  471. Mathf.Lerp(oldArea.x, newArea.x, xLerp),
  472. newArea.y,
  473. Mathf.Lerp(oldArea.width, newArea.width, xLerp),
  474. newArea.height
  475. );
  476. }
  477. if (newArea.height < oldArea.height - epsilon)
  478. {
  479. float yLerp = Mathf.InverseLerp(oldArea.height, newArea.height, vScaleMin);
  480. newArea = new Rect(
  481. newArea.x,
  482. Mathf.Lerp(oldArea.y, newArea.y, yLerp),
  483. newArea.width,
  484. Mathf.Lerp(oldArea.height, newArea.height, yLerp)
  485. );
  486. }
  487. if (newArea.width > oldArea.width + epsilon)
  488. {
  489. float xLerp = Mathf.InverseLerp(oldArea.width, newArea.width, hScaleMax);
  490. newArea = new Rect(
  491. Mathf.Lerp(oldArea.x, newArea.x, xLerp),
  492. newArea.y,
  493. Mathf.Lerp(oldArea.width, newArea.width, xLerp),
  494. newArea.height
  495. );
  496. }
  497. if (newArea.height > oldArea.height + epsilon)
  498. {
  499. float yLerp = Mathf.InverseLerp(oldArea.height, newArea.height, vScaleMax);
  500. newArea = new Rect(
  501. newArea.x,
  502. Mathf.Lerp(oldArea.y, newArea.y, yLerp),
  503. newArea.width,
  504. Mathf.Lerp(oldArea.height, newArea.height, yLerp)
  505. );
  506. }
  507. // Enforce ranges
  508. if (newArea.xMin < hRangeMin)
  509. newArea.x = hRangeMin;
  510. if (newArea.xMax > hRangeMax)
  511. newArea.x = hRangeMax - newArea.width;
  512. if (newArea.yMin < vRangeMin)
  513. newArea.y = vRangeMin;
  514. if (newArea.yMax > vRangeMax)
  515. newArea.y = vRangeMax - newArea.height;
  516. shownAreaInsideMarginsInternal = newArea;
  517. m_LastShownAreaInsideMargins = newArea;
  518. }
  519. public float PixelToTime(float pixelX, Rect rect)
  520. {
  521. return ((pixelX - rect.x) * shownArea.width / rect.width + shownArea.x);
  522. }
  523. public float TimeToPixel(float time, Rect rect)
  524. {
  525. return (time - shownArea.x) / shownArea.width * rect.width + rect.x;
  526. }
  527. public float PixelDeltaToTime(Rect rect)
  528. {
  529. return shownArea.width / rect.width;
  530. }
  531. }