Sprite.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659
  1. topSuite("Ext.draw.sprite.Sprite", ['Ext.draw.*'], function() {
  2. beforeEach(function() {
  3. // Silence warnings regarding Sencha download server
  4. spyOn(Ext.log, 'warn');
  5. });
  6. describe('setAttributes', function () {
  7. var draw;
  8. afterEach(function () {
  9. Ext.destroy(draw);
  10. });
  11. it('should filter out attributes properly', function () {
  12. // This actually tests many things: the setAttributes method of the sprite,
  13. // the abstract and Animation modifiers and the Animator class.
  14. var animationend;
  15. draw = new Ext.draw.Container({
  16. renderTo: document.body,
  17. width: 200,
  18. height: 200
  19. });
  20. var surface = draw.getSurface();
  21. var circle = new Ext.draw.sprite.Circle({
  22. r: 10,
  23. cx: 100,
  24. cy: 100,
  25. fillStyle: 'red',
  26. strokeStyle: 'none'
  27. });
  28. var animation = circle.getAnimation();
  29. animation.setDuration(250);
  30. animation.on('animationend', function () {
  31. animationend = true;
  32. });
  33. surface.add(circle);
  34. circle.setAttributes({
  35. r: 90
  36. });
  37. surface.renderFrame();
  38. waitsFor(function () {
  39. return animationend;
  40. });
  41. runs(function () {
  42. expect(circle.attr.r).toBe(90);
  43. animationend = false;
  44. circle.setAttributes({
  45. r: 50
  46. });
  47. // The call below should compare the value being set with the target value
  48. // at the end of animation, not the current value, which is still 90.
  49. // If this is buggy, the attribute won't be set and the animation to 50
  50. // will be performed instead.
  51. circle.setAttributes({
  52. r: 90
  53. });
  54. surface.renderFrame();
  55. });
  56. waitsFor(function () {
  57. return animationend;
  58. });
  59. runs(function () {
  60. expect(circle.attr.r).toBe(90);
  61. });
  62. });
  63. });
  64. describe('surface', function () {
  65. var surface;
  66. it('should remove itself from the old surface', function () {
  67. surface = new Ext.draw.Surface({
  68. items: {
  69. type: 'rect',
  70. id: 'rect',
  71. x: 50,
  72. y: 50,
  73. width: 100,
  74. height: 100,
  75. fillStyle: 'orange'
  76. }
  77. });
  78. var sprite = surface.get('rect');
  79. expect(surface.getItems().length).toBe(1);
  80. sprite.setSurface(null);
  81. expect(surface.getItems().length).toBe(0);
  82. });
  83. afterEach(function () {
  84. Ext.destroy(surface);
  85. });
  86. });
  87. describe('transformation matrix calculation', function () {
  88. describe('default centers of scaling and rotation', function () {
  89. it('should apply transformation in the following order: scale, rotate, translate', function () {
  90. var theta = Math.PI / 2,
  91. sin = Math.sin(theta),
  92. cos = Math.cos(theta),
  93. left = 100,
  94. top = 100,
  95. width = 100,
  96. height = 100,
  97. sx = 2,
  98. sy = 0.5,
  99. tx = 100,
  100. ty = 50,
  101. centerX = left + width / 2,
  102. centerY = top + height / 2;
  103. var rect = new Ext.draw.sprite.Rect({
  104. x: left,
  105. y: top,
  106. width: width,
  107. height: height,
  108. rotationRads: theta,
  109. scalingX: sx,
  110. scalingY: sy,
  111. translationX: tx,
  112. translationY: ty
  113. });
  114. var referenceMatrix = [
  115. cos * sx, sin * sx,
  116. -sin * sy, cos * sy,
  117. cos * (centerX * (1 - sx) - centerX) - sin * (centerY * (1 - sy) - centerY) + centerX + tx,
  118. sin * (centerX * (1 - sx) - centerX) + cos * (centerY * (1 - sy) - centerY) + centerY + ty
  119. ];
  120. rect.applyTransformations(true);
  121. expect(rect.attr.matrix.elements).toEqual(referenceMatrix);
  122. });
  123. });
  124. describe('custom centers of scaling and rotation', function () {
  125. it('should apply transformation in the following order: scale, rotate, translate', function () {
  126. var theta = Math.PI / 2,
  127. sin = Math.sin(theta),
  128. cos = Math.cos(theta),
  129. left = 100,
  130. top = 100,
  131. width = 100,
  132. height = 100,
  133. sx = 2,
  134. sy = 0.5,
  135. tx = 100,
  136. ty = 50,
  137. scalingCenterX = 50,
  138. scalingCenterY = 50,
  139. rotationCenterX = 150,
  140. rotationCenterY = 150;
  141. var rect = new Ext.draw.sprite.Rect({
  142. x: left,
  143. y: top,
  144. width: width,
  145. height: height,
  146. rotationRads: theta,
  147. scalingX: sx,
  148. scalingY: sy,
  149. translationX: tx,
  150. translationY: ty,
  151. rotationCenterX: rotationCenterX,
  152. rotationCenterY: rotationCenterY,
  153. scalingCenterX: scalingCenterX,
  154. scalingCenterY: scalingCenterY
  155. });
  156. var referenceMatrix = [
  157. cos * sx, sin * sx,
  158. -sin * sy, cos * sy,
  159. cos * (scalingCenterX * (1 - sx) - rotationCenterX) - sin * (scalingCenterY * (1 - sy) - rotationCenterY) + rotationCenterX + tx,
  160. sin * (scalingCenterX * (1 - sx) - rotationCenterX) + cos * (scalingCenterY * (1 - sy) - rotationCenterY) + rotationCenterY + ty
  161. ];
  162. rect.applyTransformations(true);
  163. expect(rect.attr.matrix.elements).toEqual(referenceMatrix);
  164. });
  165. });
  166. });
  167. describe('setTransform', function () {
  168. // This is a result of scaling by (2.5, 7.5), rotating by Math.PI/4 and translating by (3,4).
  169. var elements = [1.76776695, 1.76776695, -5.30330086, 5.30330086, 3, 4],
  170. sprite;
  171. beforeEach(function () {
  172. sprite = new Ext.draw.sprite.Rect();
  173. });
  174. afterEach(function () {
  175. Ext.destroy(sprite);
  176. });
  177. it("should use the given elements for the transformation matrix of the sprite", function () {
  178. sprite.setTransform(elements);
  179. var matrixElements = sprite.attr.matrix.elements;
  180. expect(matrixElements).toEqual(elements);
  181. });
  182. it("should mark the sprite and its parent as dirty", function () {
  183. var drawContainer = new Ext.draw.Container({
  184. renderTo: Ext.getBody(),
  185. width: 200,
  186. height: 200
  187. });
  188. var surface = drawContainer.getSurface();
  189. expect(surface.getDirty()).toBe(false);
  190. surface.add(sprite);
  191. expect(surface.getDirty()).toBe(true);
  192. surface.renderFrame();
  193. expect(surface.getDirty()).toBe(false);
  194. sprite.setTransform(elements);
  195. expect(sprite.attr.dirty).toBe(true);
  196. expect(sprite.getParent().getDirty()).toBe(true);
  197. drawContainer.destroy();
  198. });
  199. it("should properly calculate the inverse matrix from the given matrix", function () {
  200. sprite.setTransform(elements);
  201. var inverseMatrixElements = sprite.attr.inverseMatrix.elements,
  202. precision = 8;
  203. expect(inverseMatrixElements[0]).toBeCloseTo(0.28284271, precision);
  204. expect(inverseMatrixElements[1]).toBeCloseTo(-0.0942809, precision);
  205. expect(inverseMatrixElements[2]).toBeCloseTo(0.28284271, precision);
  206. expect(inverseMatrixElements[3]).toBeCloseTo(0.0942809, precision);
  207. expect(inverseMatrixElements[4]).toBeCloseTo(-1.97989899, precision);
  208. expect(inverseMatrixElements[5]).toBeCloseTo(-0.0942809, precision);
  209. });
  210. it("should mark bbox transform as dirty", function () {
  211. sprite.setTransform(elements);
  212. expect(sprite.attr.bbox.transform.dirty).toBe(true);
  213. });
  214. it("should not update the transformation attributes by default", function () {
  215. var attr = sprite.attr,
  216. rotationRads = attr.rotationRads,
  217. rotationCenterX = attr.rotationCenterX,
  218. rotationCenterY = attr.rotationCenterY,
  219. scalingX = attr.scalingX,
  220. scalingY = attr.scalingY,
  221. scalingCenterX = attr.scalingCenterX,
  222. scalingCenterY = attr.scalingCenterY,
  223. translationX = attr.translationX,
  224. translationY = attr.translationY;
  225. sprite.setTransform(elements);
  226. expect(attr.rotationRads).toEqual(rotationRads);
  227. expect(attr.rotationCenterX).toEqual(rotationCenterX);
  228. expect(attr.rotationCenterY).toEqual(rotationCenterY);
  229. expect(attr.scalingX).toEqual(scalingX);
  230. expect(attr.scalingY).toEqual(scalingY);
  231. expect(attr.scalingCenterX).toEqual(scalingCenterX);
  232. expect(attr.scalingCenterY).toEqual(scalingCenterY);
  233. expect(attr.translationX).toEqual(translationX);
  234. expect(attr.translationY).toEqual(translationY);
  235. });
  236. it("should update the transformation attributes, if explicitly asked", function () {
  237. var attr = sprite.attr,
  238. precision = 8;
  239. sprite.setTransform(elements, true);
  240. expect(attr.rotationRads).toBeCloseTo(Math.PI / 4, precision);
  241. expect(attr.rotationCenterX).toEqual(0);
  242. expect(attr.rotationCenterY).toEqual(0);
  243. expect(attr.scalingX).toBeCloseTo(2.5, precision);
  244. expect(attr.scalingY).toBeCloseTo(7.5, precision);
  245. expect(attr.scalingCenterX).toEqual(0);
  246. expect(attr.scalingCenterY).toEqual(0);
  247. expect(attr.translationX).toEqual(3);
  248. expect(attr.translationY).toEqual(4);
  249. });
  250. it("should not modify the given array", function () {
  251. sprite.setTransform(elements);
  252. sprite.attr.matrix.rotate(Math.PI / 4);
  253. expect(elements).toEqual([1.76776695, 1.76776695, -5.30330086, 5.30330086, 3, 4]);
  254. });
  255. it("should return the sprite itself", function () {
  256. var result = sprite.transform([1, 0, 0, 1, 100, 100]);
  257. expect(result).toEqual(sprite);
  258. });
  259. });
  260. describe('resetTransform', function () {
  261. var spriteConfig = {
  262. type: 'rect',
  263. x: 0,
  264. y: 0,
  265. width: 100,
  266. height: 100,
  267. rotationCenterX: 0,
  268. rotationCenterY: 0,
  269. rotationRads: Math.PI / 3,
  270. scalingCenterX: 0,
  271. scalingCenterY: 0,
  272. scalingX: 2,
  273. scalingY: 3,
  274. translationX: 50,
  275. translationY: 50
  276. };
  277. it("should mark the sprite and its parent as dirty", function () {
  278. var drawContainer = new Ext.draw.Container({
  279. renderTo: Ext.getBody(),
  280. width: 200,
  281. height: 200
  282. });
  283. var surface = drawContainer.getSurface();
  284. expect(surface.getDirty()).toBe(false);
  285. var sprite = surface.add(spriteConfig);
  286. expect(surface.getDirty()).toBe(true);
  287. surface.renderFrame();
  288. expect(surface.getDirty()).toBe(false);
  289. sprite.resetTransform();
  290. expect(sprite.attr.dirty).toBe(true);
  291. expect(sprite.getParent().getDirty()).toBe(true);
  292. drawContainer.destroy();
  293. });
  294. it("should reset the transformation matrix and its reverse to the identity matrix", function () {
  295. var sprite = new Ext.draw.sprite.Rect(spriteConfig),
  296. identityMatrixElements = [1, 0, 0, 1, 0, 0];
  297. sprite.applyTransformations(true);
  298. expect(sprite.attr.matrix.elements).not.toEqual(identityMatrixElements);
  299. sprite.resetTransform();
  300. expect(sprite.attr.matrix.elements).toEqual(identityMatrixElements);
  301. expect(sprite.attr.inverseMatrix.elements).toEqual(identityMatrixElements);
  302. sprite.destroy();
  303. });
  304. it("should return the sprite itself", function () {
  305. var sprite = new Ext.draw.sprite.Rect(),
  306. result = sprite.transform([1, 0, 0, 1, 100, 100]);
  307. expect(result).toEqual(sprite);
  308. sprite.destroy();
  309. });
  310. });
  311. describe('transform', function () {
  312. it("should multiply the given matrix with the current transformation matrix", function () {
  313. var sprite = new Ext.draw.sprite.Rect(),
  314. precision = 12;
  315. sprite.attr.matrix.elements = [1, 2, 3, 4, 5, 6];
  316. sprite.transform([1, 2, 3, 4, 5, 6]);
  317. expect(sprite.attr.matrix.elements).toEqual([7, 10, 15, 22, 28, 40]);
  318. var inverseMatrixElements = sprite.attr.inverseMatrix.elements;
  319. expect(inverseMatrixElements[0]).toBeCloseTo(5.5, precision);
  320. expect(inverseMatrixElements[1]).toBeCloseTo(-2.5, precision);
  321. expect(inverseMatrixElements[2]).toBeCloseTo(-3.75, precision);
  322. expect(inverseMatrixElements[3]).toBeCloseTo(1.75, precision);
  323. expect(inverseMatrixElements[4]).toBeCloseTo(-4, precision);
  324. expect(inverseMatrixElements[5]).toBeCloseTo(0, precision);
  325. });
  326. it("should pre-multiply the current matrix with the given matrix", function () {
  327. var sprite = new Ext.draw.sprite.Rect(),
  328. scale = [2, 0, 0, 3, 0, 0],
  329. translate = [1, 0, 0, 1, 100, 100],
  330. p = [2, 4],
  331. tp;
  332. // Initially, sprite's matrix is identity matrix.
  333. // First scale the grid, then translate.
  334. // S * T * I = identity.prepend(translate).prepend(scale)
  335. sprite.transform(translate).transform(scale);
  336. expect(sprite.attr.matrix.elements).toEqual([2, 0, 0, 3, 200, 300]);
  337. tp = sprite.attr.matrix.transformPoint(p);
  338. // Transformed point in original grid coordinates.
  339. expect(tp).toEqual([204, 312]);
  340. sprite.resetTransform();
  341. // First translate the grid, then scale.
  342. // T * S * I = identity.prepend(scale).prepend(translate)
  343. sprite.transform(scale).transform(translate);
  344. expect(sprite.attr.matrix.elements).toEqual([2, 0, 0, 3, 100, 100]);
  345. tp = sprite.attr.matrix.transformPoint(p);
  346. expect(tp).toEqual([104, 112]);
  347. sprite.destroy();
  348. });
  349. it("should return the sprite itself", function () {
  350. var sprite = new Ext.draw.sprite.Rect(),
  351. result = sprite.transform([1, 0, 0, 1, 100, 100]);
  352. expect(result).toEqual(sprite);
  353. sprite.destroy();
  354. });
  355. });
  356. describe('remove', function () {
  357. it("should remove itself from the surface, returning itself or null (if already removed)", function () {
  358. var surface = new Ext.draw.Surface({}),
  359. sprite = new Ext.draw.sprite.Rect({}),
  360. id = sprite.getId(),
  361. result;
  362. surface.add(sprite);
  363. result = sprite.remove();
  364. expect(surface.getItems().length).toBe(0);
  365. expect(surface.get(id)).toBe(undefined);
  366. expect(result).toEqual(sprite);
  367. result = sprite.remove(); // sprite with no surface, expect not to throw
  368. expect(result).toBe(null);
  369. sprite.destroy();
  370. surface.destroy();
  371. });
  372. });
  373. describe('destroy', function () {
  374. it("should remove itself from the surface", function () {
  375. var surface = new Ext.draw.Surface({}),
  376. sprite = new Ext.draw.sprite.Rect({}),
  377. id = sprite.getId();
  378. surface.add(sprite);
  379. sprite.destroy();
  380. expect(surface.getItems().length).toBe(0);
  381. expect(surface.get(id)).toBe(undefined);
  382. surface.destroy();
  383. });
  384. });
  385. describe("isVisible", function () {
  386. var none = 'none',
  387. rgba_none = 'rgba(0,0,0,0)',
  388. sprite, surface, container;
  389. beforeEach(function () {
  390. container = new Ext.draw.Container({
  391. renderTo: Ext.getBody()
  392. });
  393. surface = new Ext.draw.Surface();
  394. sprite = new Ext.draw.sprite.Rect({
  395. hidden: false,
  396. globalAlpha: 1,
  397. fillOpacity: 1,
  398. strokeOpacity: 1,
  399. fillStyle: 'red',
  400. strokeStyle: 'red'
  401. });
  402. surface.add(sprite);
  403. container.add(surface);
  404. });
  405. afterEach(function () {
  406. Ext.destroy(sprite, surface, container);
  407. });
  408. it("should return true if the sprite belongs to a visible parent, false otherwise", function () {
  409. expect(sprite.isVisible()).toBe(true);
  410. surface.remove(sprite);
  411. expect(sprite.isVisible()).toBe(false);
  412. var instancing = new Ext.draw.sprite.Instancing({
  413. template: sprite
  414. });
  415. surface.add(instancing);
  416. expect(sprite.isVisible()).toBe(true);
  417. instancing.destroy();
  418. });
  419. it("should return false if the sprite belongs to a parent that doesn't belong to a surface", function () {
  420. var instancing = new Ext.draw.sprite.Instancing({
  421. template: sprite
  422. });
  423. expect(sprite.isVisible()).toBe(false);
  424. });
  425. it("should return false in case the sprite is hidden", function () {
  426. sprite.hide();
  427. expect(sprite.isVisible()).toBe(false);
  428. });
  429. it("should return false in case the sprite has no fillStyle and strokeStyle, true otherwise", function () {
  430. sprite.setAttributes({
  431. fillStyle: none
  432. });
  433. expect(sprite.isVisible()).toBe(true);
  434. sprite.setAttributes({
  435. fillStyle: rgba_none
  436. });
  437. expect(sprite.isVisible()).toBe(true);
  438. sprite.setAttributes({
  439. fillStyle: 'red',
  440. strokeStyle: none
  441. });
  442. expect(sprite.isVisible()).toBe(true);
  443. sprite.setAttributes({
  444. strokeStyle: rgba_none
  445. });
  446. expect(sprite.isVisible()).toBe(true);
  447. sprite.setAttributes({
  448. fillStyle: none,
  449. strokeStyle: none
  450. });
  451. expect(sprite.isVisible()).toBe(false);
  452. sprite.setAttributes({
  453. fillStyle: none,
  454. strokeStyle: rgba_none
  455. });
  456. expect(sprite.isVisible()).toBe(false);
  457. sprite.setAttributes({
  458. fillStyle: rgba_none,
  459. strokeStyle: none
  460. });
  461. expect(sprite.isVisible()).toBe(false);
  462. sprite.setAttributes({
  463. fillStyle: rgba_none,
  464. strokeStyle: rgba_none
  465. });
  466. expect(sprite.isVisible()).toBe(false);
  467. });
  468. it("should return false if the globalAlpha attribute is zero", function () {
  469. sprite.setAttributes({
  470. globalAlpha: 0
  471. });
  472. expect(sprite.isVisible()).toBe(false);
  473. });
  474. it("should return false if both fill and stroke are completely transparent, true otherwise", function () {
  475. sprite.setAttributes({
  476. fillOpacity: 0,
  477. strokeOpacity: 0
  478. });
  479. expect(sprite.isVisible()).toBe(false);
  480. sprite.setAttributes({
  481. fillOpacity: 0,
  482. strokeOpacity: 0.01
  483. });
  484. expect(sprite.isVisible()).toBe(true);
  485. sprite.setAttributes({
  486. fillOpacity: 0.01,
  487. strokeOpacity: 0
  488. });
  489. expect(sprite.isVisible()).toBe(true);
  490. });
  491. });
  492. describe("hitTest", function () {
  493. var sprite, surface, container;
  494. beforeEach(function () {
  495. container = new Ext.draw.Container({
  496. renderTo: Ext.getBody()
  497. });
  498. surface = new Ext.draw.Surface();
  499. sprite = new Ext.draw.sprite.Circle({
  500. hidden: false,
  501. globalAlpha: 1,
  502. fillOpacity: 1,
  503. strokeOpacity: 1,
  504. fillStyle: 'red',
  505. strokeStyle: 'red',
  506. r: 100,
  507. cx: 100,
  508. cy: 100
  509. });
  510. surface.add(sprite);
  511. container.add(surface);
  512. });
  513. afterEach(function () {
  514. Ext.destroy(sprite, surface, container);
  515. });
  516. it("should return an object with the 'sprite' property set to the sprite itself, " +
  517. "if the sprite is visible and its bounding box is hit", function () {
  518. // Testing hitTest method of the abstract Sprite class.
  519. // Even though, (10,10) is not inside the circle, it's inside it's bounding box.
  520. var result = Ext.draw.sprite.Sprite.prototype.hitTest.call(sprite, [10, 10]);
  521. expect(result && result.sprite).toBe(sprite);
  522. });
  523. it("should return null, if the sprite's bounding box is hit, but the sprite is not visible", function () {
  524. var originalMethod = sprite.isVisible;
  525. sprite.isVisible = function () { return false; };
  526. var result = Ext.draw.sprite.Sprite.prototype.hitTest.call(sprite, [10, 10]);
  527. expect(result).toBe(null);
  528. sprite.isVisible = originalMethod;
  529. });
  530. it("should return null, if the sprite is visible, but it's bounding box is not hit", function () {
  531. var result = Ext.draw.sprite.Sprite.prototype.hitTest.call(sprite, [210, 210]);
  532. expect(result).toBe(null);
  533. });
  534. });
  535. describe("getAnimation", function () {
  536. it("should return the stored reference to the sprite's animation modifier", function () {
  537. var sprite = new Ext.draw.sprite.Rect();
  538. expect(sprite.getAnimation()).toEqual(sprite.modifiers.animation);
  539. });
  540. });
  541. describe("setAnimation", function () {
  542. it("should set the config of the Animation modifier of a sprite", function () {
  543. var sprite = new Ext.draw.sprite.Rect();
  544. var config = {
  545. duration: 2000,
  546. easing: 'bounceOut',
  547. customEasings: {
  548. x: 'linear'
  549. },
  550. customDurations: {
  551. y: 1000
  552. }
  553. };
  554. sprite.setAnimation(config);
  555. var actualConfig = sprite.modifiers.animation.getInitialConfig();
  556. expect(actualConfig.duration).toEqual(config.duration);
  557. expect(actualConfig.easing).toEqual(config.easing);
  558. expect(actualConfig.customEasings).toEqual(config.customEasings);
  559. expect(actualConfig.customDurations).toEqual(config.customDurations);
  560. });
  561. });
  562. });