Source: Window.js

Window.js

  1. //-----------------------------------------------------------------------------
  2. /**
  3. * The window in the game.
  4. *
  5. * @class
  6. * @extends PIXI.Container
  7. */
  8. function Window() {
  9. this.initialize(...arguments);
  10. }
  11. Window.prototype = Object.create(PIXI.Container.prototype);
  12. Window.prototype.constructor = Window;
  13. Window.prototype.initialize = function() {
  14. PIXI.Container.call(this);
  15. this._isWindow = true;
  16. this._windowskin = null;
  17. this._width = 0;
  18. this._height = 0;
  19. this._cursorRect = new Rectangle();
  20. this._openness = 255;
  21. this._animationCount = 0;
  22. this._padding = 12;
  23. this._margin = 4;
  24. this._colorTone = [0, 0, 0, 0];
  25. this._innerChildren = [];
  26. this._container = null;
  27. this._backSprite = null;
  28. this._frameSprite = null;
  29. this._contentsBackSprite = null;
  30. this._cursorSprite = null;
  31. this._contentsSprite = null;
  32. this._downArrowSprite = null;
  33. this._upArrowSprite = null;
  34. this._pauseSignSprite = null;
  35. this._createAllParts();
  36. /**
  37. * The origin point of the window for scrolling.
  38. *
  39. * @type Point
  40. */
  41. this.origin = new Point();
  42. /**
  43. * The active state for the window.
  44. *
  45. * @type boolean
  46. */
  47. this.active = true;
  48. /**
  49. * The visibility of the frame.
  50. *
  51. * @type boolean
  52. */
  53. this.frameVisible = true;
  54. /**
  55. * The visibility of the cursor.
  56. *
  57. * @type boolean
  58. */
  59. this.cursorVisible = true;
  60. /**
  61. * The visibility of the down scroll arrow.
  62. *
  63. * @type boolean
  64. */
  65. this.downArrowVisible = false;
  66. /**
  67. * The visibility of the up scroll arrow.
  68. *
  69. * @type boolean
  70. */
  71. this.upArrowVisible = false;
  72. /**
  73. * The visibility of the pause sign.
  74. *
  75. * @type boolean
  76. */
  77. this.pause = false;
  78. };
  79. /**
  80. * The image used as a window skin.
  81. *
  82. * @type Bitmap
  83. * @name Window#windowskin
  84. */
  85. Object.defineProperty(Window.prototype, "windowskin", {
  86. get: function() {
  87. return this._windowskin;
  88. },
  89. set: function(value) {
  90. if (this._windowskin !== value) {
  91. this._windowskin = value;
  92. this._windowskin.addLoadListener(this._onWindowskinLoad.bind(this));
  93. }
  94. },
  95. configurable: true
  96. });
  97. /**
  98. * The bitmap used for the window contents.
  99. *
  100. * @type Bitmap
  101. * @name Window#contents
  102. */
  103. Object.defineProperty(Window.prototype, "contents", {
  104. get: function() {
  105. return this._contentsSprite.bitmap;
  106. },
  107. set: function(value) {
  108. this._contentsSprite.bitmap = value;
  109. },
  110. configurable: true
  111. });
  112. /**
  113. * The bitmap used for the window contents background.
  114. *
  115. * @type Bitmap
  116. * @name Window#contentsBack
  117. */
  118. Object.defineProperty(Window.prototype, "contentsBack", {
  119. get: function() {
  120. return this._contentsBackSprite.bitmap;
  121. },
  122. set: function(value) {
  123. this._contentsBackSprite.bitmap = value;
  124. },
  125. configurable: true
  126. });
  127. /**
  128. * The width of the window in pixels.
  129. *
  130. * @type number
  131. * @name Window#width
  132. */
  133. Object.defineProperty(Window.prototype, "width", {
  134. get: function() {
  135. return this._width;
  136. },
  137. set: function(value) {
  138. this._width = value;
  139. this._refreshAllParts();
  140. },
  141. configurable: true
  142. });
  143. /**
  144. * The height of the window in pixels.
  145. *
  146. * @type number
  147. * @name Window#height
  148. */
  149. Object.defineProperty(Window.prototype, "height", {
  150. get: function() {
  151. return this._height;
  152. },
  153. set: function(value) {
  154. this._height = value;
  155. this._refreshAllParts();
  156. },
  157. configurable: true
  158. });
  159. /**
  160. * The size of the padding between the frame and contents.
  161. *
  162. * @type number
  163. * @name Window#padding
  164. */
  165. Object.defineProperty(Window.prototype, "padding", {
  166. get: function() {
  167. return this._padding;
  168. },
  169. set: function(value) {
  170. this._padding = value;
  171. this._refreshAllParts();
  172. },
  173. configurable: true
  174. });
  175. /**
  176. * The size of the margin for the window background.
  177. *
  178. * @type number
  179. * @name Window#margin
  180. */
  181. Object.defineProperty(Window.prototype, "margin", {
  182. get: function() {
  183. return this._margin;
  184. },
  185. set: function(value) {
  186. this._margin = value;
  187. this._refreshAllParts();
  188. },
  189. configurable: true
  190. });
  191. /**
  192. * The opacity of the window without contents (0 to 255).
  193. *
  194. * @type number
  195. * @name Window#opacity
  196. */
  197. Object.defineProperty(Window.prototype, "opacity", {
  198. get: function() {
  199. return this._container.alpha * 255;
  200. },
  201. set: function(value) {
  202. this._container.alpha = value.clamp(0, 255) / 255;
  203. },
  204. configurable: true
  205. });
  206. /**
  207. * The opacity of the window background (0 to 255).
  208. *
  209. * @type number
  210. * @name Window#backOpacity
  211. */
  212. Object.defineProperty(Window.prototype, "backOpacity", {
  213. get: function() {
  214. return this._backSprite.alpha * 255;
  215. },
  216. set: function(value) {
  217. this._backSprite.alpha = value.clamp(0, 255) / 255;
  218. },
  219. configurable: true
  220. });
  221. /**
  222. * The opacity of the window contents (0 to 255).
  223. *
  224. * @type number
  225. * @name Window#contentsOpacity
  226. */
  227. Object.defineProperty(Window.prototype, "contentsOpacity", {
  228. get: function() {
  229. return this._contentsSprite.alpha * 255;
  230. },
  231. set: function(value) {
  232. this._contentsSprite.alpha = value.clamp(0, 255) / 255;
  233. },
  234. configurable: true
  235. });
  236. /**
  237. * The openness of the window (0 to 255).
  238. *
  239. * @type number
  240. * @name Window#openness
  241. */
  242. Object.defineProperty(Window.prototype, "openness", {
  243. get: function() {
  244. return this._openness;
  245. },
  246. set: function(value) {
  247. if (this._openness !== value) {
  248. this._openness = value.clamp(0, 255);
  249. this._container.scale.y = this._openness / 255;
  250. this._container.y = (this.height / 2) * (1 - this._openness / 255);
  251. }
  252. },
  253. configurable: true
  254. });
  255. /**
  256. * The width of the content area in pixels.
  257. *
  258. * @readonly
  259. * @type number
  260. * @name Window#innerWidth
  261. */
  262. Object.defineProperty(Window.prototype, "innerWidth", {
  263. get: function() {
  264. return Math.max(0, this._width - this._padding * 2);
  265. },
  266. configurable: true
  267. });
  268. /**
  269. * The height of the content area in pixels.
  270. *
  271. * @readonly
  272. * @type number
  273. * @name Window#innerHeight
  274. */
  275. Object.defineProperty(Window.prototype, "innerHeight", {
  276. get: function() {
  277. return Math.max(0, this._height - this._padding * 2);
  278. },
  279. configurable: true
  280. });
  281. /**
  282. * The rectangle of the content area.
  283. *
  284. * @readonly
  285. * @type Rectangle
  286. * @name Window#innerRect
  287. */
  288. Object.defineProperty(Window.prototype, "innerRect", {
  289. get: function() {
  290. return new Rectangle(
  291. this._padding,
  292. this._padding,
  293. this.innerWidth,
  294. this.innerHeight
  295. );
  296. },
  297. configurable: true
  298. });
  299. /**
  300. * Destroys the window.
  301. */
  302. Window.prototype.destroy = function() {
  303. const options = { children: true, texture: true };
  304. PIXI.Container.prototype.destroy.call(this, options);
  305. };
  306. /**
  307. * Updates the window for each frame.
  308. */
  309. Window.prototype.update = function() {
  310. if (this.active) {
  311. this._animationCount++;
  312. }
  313. for (const child of this.children) {
  314. if (child.update) {
  315. child.update();
  316. }
  317. }
  318. };
  319. /**
  320. * Sets the x, y, width, and height all at once.
  321. *
  322. * @param {number} x - The x coordinate of the window.
  323. * @param {number} y - The y coordinate of the window.
  324. * @param {number} width - The width of the window.
  325. * @param {number} height - The height of the window.
  326. */
  327. Window.prototype.move = function(x, y, width, height) {
  328. this.x = x || 0;
  329. this.y = y || 0;
  330. if (this._width !== width || this._height !== height) {
  331. this._width = width || 0;
  332. this._height = height || 0;
  333. this._refreshAllParts();
  334. }
  335. };
  336. /**
  337. * Checks whether the window is completely open (openness == 255).
  338. *
  339. * @returns {boolean} True if the window is open.
  340. */
  341. Window.prototype.isOpen = function() {
  342. return this._openness >= 255;
  343. };
  344. /**
  345. * Checks whether the window is completely closed (openness == 0).
  346. *
  347. * @returns {boolean} True if the window is closed.
  348. */
  349. Window.prototype.isClosed = function() {
  350. return this._openness <= 0;
  351. };
  352. /**
  353. * Sets the position of the command cursor.
  354. *
  355. * @param {number} x - The x coordinate of the cursor.
  356. * @param {number} y - The y coordinate of the cursor.
  357. * @param {number} width - The width of the cursor.
  358. * @param {number} height - The height of the cursor.
  359. */
  360. Window.prototype.setCursorRect = function(x, y, width, height) {
  361. const cw = Math.floor(width || 0);
  362. const ch = Math.floor(height || 0);
  363. this._cursorRect.x = Math.floor(x || 0);
  364. this._cursorRect.y = Math.floor(y || 0);
  365. if (this._cursorRect.width !== cw || this._cursorRect.height !== ch) {
  366. this._cursorRect.width = cw;
  367. this._cursorRect.height = ch;
  368. this._refreshCursor();
  369. }
  370. };
  371. /**
  372. * Moves the cursor position by the given amount.
  373. *
  374. * @param {number} x - The amount of horizontal movement.
  375. * @param {number} y - The amount of vertical movement.
  376. */
  377. Window.prototype.moveCursorBy = function(x, y) {
  378. this._cursorRect.x += x;
  379. this._cursorRect.y += y;
  380. };
  381. /**
  382. * Moves the inner children by the given amount.
  383. *
  384. * @param {number} x - The amount of horizontal movement.
  385. * @param {number} y - The amount of vertical movement.
  386. */
  387. Window.prototype.moveInnerChildrenBy = function(x, y) {
  388. for (const child of this._innerChildren) {
  389. child.x += x;
  390. child.y += y;
  391. }
  392. };
  393. /**
  394. * Changes the color of the background.
  395. *
  396. * @param {number} r - The red value in the range (-255, 255).
  397. * @param {number} g - The green value in the range (-255, 255).
  398. * @param {number} b - The blue value in the range (-255, 255).
  399. */
  400. Window.prototype.setTone = function(r, g, b) {
  401. const tone = this._colorTone;
  402. if (r !== tone[0] || g !== tone[1] || b !== tone[2]) {
  403. this._colorTone = [r, g, b, 0];
  404. this._refreshBack();
  405. }
  406. };
  407. /**
  408. * Adds a child between the background and contents.
  409. *
  410. * @param {object} child - The child to add.
  411. * @returns {object} The child that was added.
  412. */
  413. Window.prototype.addChildToBack = function(child) {
  414. const containerIndex = this.children.indexOf(this._container);
  415. return this.addChildAt(child, containerIndex + 1);
  416. };
  417. /**
  418. * Adds a child to the client area.
  419. *
  420. * @param {object} child - The child to add.
  421. * @returns {object} The child that was added.
  422. */
  423. Window.prototype.addInnerChild = function(child) {
  424. this._innerChildren.push(child);
  425. return this._clientArea.addChild(child);
  426. };
  427. /**
  428. * Updates the transform on all children of this container for rendering.
  429. */
  430. Window.prototype.updateTransform = function() {
  431. this._updateClientArea();
  432. this._updateFrame();
  433. this._updateContentsBack();
  434. this._updateCursor();
  435. this._updateContents();
  436. this._updateArrows();
  437. this._updatePauseSign();
  438. PIXI.Container.prototype.updateTransform.call(this);
  439. this._updateFilterArea();
  440. };
  441. /**
  442. * Draws the window shape into PIXI.Graphics object. Used by WindowLayer.
  443. */
  444. Window.prototype.drawShape = function(graphics) {
  445. if (graphics) {
  446. const width = this.width;
  447. const height = (this.height * this._openness) / 255;
  448. const x = this.x;
  449. const y = this.y + (this.height - height) / 2;
  450. graphics.beginFill(0xffffff);
  451. graphics.drawRoundedRect(x, y, width, height, 0);
  452. graphics.endFill();
  453. }
  454. };
  455. Window.prototype._createAllParts = function() {
  456. this._createContainer();
  457. this._createBackSprite();
  458. this._createFrameSprite();
  459. this._createClientArea();
  460. this._createContentsBackSprite();
  461. this._createCursorSprite();
  462. this._createContentsSprite();
  463. this._createArrowSprites();
  464. this._createPauseSignSprites();
  465. };
  466. Window.prototype._createContainer = function() {
  467. this._container = new PIXI.Container();
  468. this.addChild(this._container);
  469. };
  470. Window.prototype._createBackSprite = function() {
  471. this._backSprite = new Sprite();
  472. this._backSprite.addChild(new TilingSprite());
  473. this._container.addChild(this._backSprite);
  474. };
  475. Window.prototype._createFrameSprite = function() {
  476. this._frameSprite = new Sprite();
  477. for (let i = 0; i < 8; i++) {
  478. this._frameSprite.addChild(new Sprite());
  479. }
  480. this._container.addChild(this._frameSprite);
  481. };
  482. Window.prototype._createClientArea = function() {
  483. this._clientArea = new Sprite();
  484. this._clientArea.filters = [new PIXI.filters.AlphaFilter()];
  485. this._clientArea.filterArea = new Rectangle();
  486. this._clientArea.move(this._padding, this._padding);
  487. this.addChild(this._clientArea);
  488. };
  489. Window.prototype._createContentsBackSprite = function() {
  490. this._contentsBackSprite = new Sprite();
  491. this._clientArea.addChild(this._contentsBackSprite);
  492. };
  493. Window.prototype._createCursorSprite = function() {
  494. this._cursorSprite = new Sprite();
  495. for (let i = 0; i < 9; i++) {
  496. this._cursorSprite.addChild(new Sprite());
  497. }
  498. this._clientArea.addChild(this._cursorSprite);
  499. };
  500. Window.prototype._createContentsSprite = function() {
  501. this._contentsSprite = new Sprite();
  502. this._clientArea.addChild(this._contentsSprite);
  503. };
  504. Window.prototype._createArrowSprites = function() {
  505. this._downArrowSprite = new Sprite();
  506. this.addChild(this._downArrowSprite);
  507. this._upArrowSprite = new Sprite();
  508. this.addChild(this._upArrowSprite);
  509. };
  510. Window.prototype._createPauseSignSprites = function() {
  511. this._pauseSignSprite = new Sprite();
  512. this.addChild(this._pauseSignSprite);
  513. };
  514. Window.prototype._onWindowskinLoad = function() {
  515. this._refreshAllParts();
  516. };
  517. Window.prototype._refreshAllParts = function() {
  518. this._refreshBack();
  519. this._refreshFrame();
  520. this._refreshCursor();
  521. this._refreshArrows();
  522. this._refreshPauseSign();
  523. };
  524. Window.prototype._refreshBack = function() {
  525. const m = this._margin;
  526. const w = Math.max(0, this._width - m * 2);
  527. const h = Math.max(0, this._height - m * 2);
  528. const sprite = this._backSprite;
  529. const tilingSprite = sprite.children[0];
  530. // [Note] We use 95 instead of 96 here to avoid blurring edges.
  531. sprite.bitmap = this._windowskin;
  532. sprite.setFrame(0, 0, 95, 95);
  533. sprite.move(m, m);
  534. sprite.scale.x = w / 95;
  535. sprite.scale.y = h / 95;
  536. tilingSprite.bitmap = this._windowskin;
  537. tilingSprite.setFrame(0, 96, 96, 96);
  538. tilingSprite.move(0, 0, w, h);
  539. tilingSprite.scale.x = 1 / sprite.scale.x;
  540. tilingSprite.scale.y = 1 / sprite.scale.y;
  541. sprite.setColorTone(this._colorTone);
  542. };
  543. Window.prototype._refreshFrame = function() {
  544. const drect = { x: 0, y: 0, width: this._width, height: this._height };
  545. const srect = { x: 96, y: 0, width: 96, height: 96 };
  546. const m = 24;
  547. for (const child of this._frameSprite.children) {
  548. child.bitmap = this._windowskin;
  549. }
  550. this._setRectPartsGeometry(this._frameSprite, srect, drect, m);
  551. };
  552. Window.prototype._refreshCursor = function() {
  553. const drect = this._cursorRect.clone();
  554. const srect = { x: 96, y: 96, width: 48, height: 48 };
  555. const m = 4;
  556. for (const child of this._cursorSprite.children) {
  557. child.bitmap = this._windowskin;
  558. }
  559. this._setRectPartsGeometry(this._cursorSprite, srect, drect, m);
  560. };
  561. Window.prototype._setRectPartsGeometry = function(sprite, srect, drect, m) {
  562. const sx = srect.x;
  563. const sy = srect.y;
  564. const sw = srect.width;
  565. const sh = srect.height;
  566. const dx = drect.x;
  567. const dy = drect.y;
  568. const dw = drect.width;
  569. const dh = drect.height;
  570. const smw = sw - m * 2;
  571. const smh = sh - m * 2;
  572. const dmw = dw - m * 2;
  573. const dmh = dh - m * 2;
  574. const children = sprite.children;
  575. sprite.setFrame(0, 0, dw, dh);
  576. sprite.move(dx, dy);
  577. // corner
  578. children[0].setFrame(sx, sy, m, m);
  579. children[1].setFrame(sx + sw - m, sy, m, m);
  580. children[2].setFrame(sx, sy + sw - m, m, m);
  581. children[3].setFrame(sx + sw - m, sy + sw - m, m, m);
  582. children[0].move(0, 0);
  583. children[1].move(dw - m, 0);
  584. children[2].move(0, dh - m);
  585. children[3].move(dw - m, dh - m);
  586. // edge
  587. children[4].move(m, 0);
  588. children[5].move(m, dh - m);
  589. children[6].move(0, m);
  590. children[7].move(dw - m, m);
  591. children[4].setFrame(sx + m, sy, smw, m);
  592. children[5].setFrame(sx + m, sy + sw - m, smw, m);
  593. children[6].setFrame(sx, sy + m, m, smh);
  594. children[7].setFrame(sx + sw - m, sy + m, m, smh);
  595. children[4].scale.x = dmw / smw;
  596. children[5].scale.x = dmw / smw;
  597. children[6].scale.y = dmh / smh;
  598. children[7].scale.y = dmh / smh;
  599. // center
  600. if (children[8]) {
  601. children[8].setFrame(sx + m, sy + m, smw, smh);
  602. children[8].move(m, m);
  603. children[8].scale.x = dmw / smw;
  604. children[8].scale.y = dmh / smh;
  605. }
  606. for (const child of children) {
  607. child.visible = dw > 0 && dh > 0;
  608. }
  609. };
  610. Window.prototype._refreshArrows = function() {
  611. const w = this._width;
  612. const h = this._height;
  613. const p = 24;
  614. const q = p / 2;
  615. const sx = 96 + p;
  616. const sy = 0 + p;
  617. this._downArrowSprite.bitmap = this._windowskin;
  618. this._downArrowSprite.anchor.x = 0.5;
  619. this._downArrowSprite.anchor.y = 0.5;
  620. this._downArrowSprite.setFrame(sx + q, sy + q + p, p, q);
  621. this._downArrowSprite.move(w / 2, h - q);
  622. this._upArrowSprite.bitmap = this._windowskin;
  623. this._upArrowSprite.anchor.x = 0.5;
  624. this._upArrowSprite.anchor.y = 0.5;
  625. this._upArrowSprite.setFrame(sx + q, sy, p, q);
  626. this._upArrowSprite.move(w / 2, q);
  627. };
  628. Window.prototype._refreshPauseSign = function() {
  629. const sx = 144;
  630. const sy = 96;
  631. const p = 24;
  632. this._pauseSignSprite.bitmap = this._windowskin;
  633. this._pauseSignSprite.anchor.x = 0.5;
  634. this._pauseSignSprite.anchor.y = 1;
  635. this._pauseSignSprite.move(this._width / 2, this._height);
  636. this._pauseSignSprite.setFrame(sx, sy, p, p);
  637. this._pauseSignSprite.alpha = 0;
  638. };
  639. Window.prototype._updateClientArea = function() {
  640. const pad = this._padding;
  641. this._clientArea.move(pad, pad);
  642. this._clientArea.x = pad - this.origin.x;
  643. this._clientArea.y = pad - this.origin.y;
  644. if (this.innerWidth > 0 && this.innerHeight > 0) {
  645. this._clientArea.visible = this.isOpen();
  646. } else {
  647. this._clientArea.visible = false;
  648. }
  649. };
  650. Window.prototype._updateFrame = function() {
  651. this._frameSprite.visible = this.frameVisible;
  652. };
  653. Window.prototype._updateContentsBack = function() {
  654. const bitmap = this._contentsBackSprite.bitmap;
  655. if (bitmap) {
  656. this._contentsBackSprite.setFrame(0, 0, bitmap.width, bitmap.height);
  657. }
  658. };
  659. Window.prototype._updateCursor = function() {
  660. this._cursorSprite.alpha = this._makeCursorAlpha();
  661. this._cursorSprite.visible = this.isOpen() && this.cursorVisible;
  662. this._cursorSprite.x = this._cursorRect.x;
  663. this._cursorSprite.y = this._cursorRect.y;
  664. };
  665. Window.prototype._makeCursorAlpha = function() {
  666. const blinkCount = this._animationCount % 40;
  667. const baseAlpha = this.contentsOpacity / 255;
  668. if (this.active) {
  669. if (blinkCount < 20) {
  670. return baseAlpha - blinkCount / 32;
  671. } else {
  672. return baseAlpha - (40 - blinkCount) / 32;
  673. }
  674. }
  675. return baseAlpha;
  676. };
  677. Window.prototype._updateContents = function() {
  678. const bitmap = this._contentsSprite.bitmap;
  679. if (bitmap) {
  680. this._contentsSprite.setFrame(0, 0, bitmap.width, bitmap.height);
  681. }
  682. };
  683. Window.prototype._updateArrows = function() {
  684. this._downArrowSprite.visible = this.isOpen() && this.downArrowVisible;
  685. this._upArrowSprite.visible = this.isOpen() && this.upArrowVisible;
  686. };
  687. Window.prototype._updatePauseSign = function() {
  688. const sprite = this._pauseSignSprite;
  689. const x = Math.floor(this._animationCount / 16) % 2;
  690. const y = Math.floor(this._animationCount / 16 / 2) % 2;
  691. const sx = 144;
  692. const sy = 96;
  693. const p = 24;
  694. if (!this.pause) {
  695. sprite.alpha = 0;
  696. } else if (sprite.alpha < 1) {
  697. sprite.alpha = Math.min(sprite.alpha + 0.1, 1);
  698. }
  699. sprite.setFrame(sx + x * p, sy + y * p, p, p);
  700. sprite.visible = this.isOpen();
  701. };
  702. Window.prototype._updateFilterArea = function() {
  703. const pos = this._clientArea.worldTransform.apply(new Point(0, 0));
  704. const filterArea = this._clientArea.filterArea;
  705. filterArea.x = pos.x + this.origin.x;
  706. filterArea.y = pos.y + this.origin.y;
  707. filterArea.width = this.innerWidth;
  708. filterArea.height = this.innerHeight;
  709. };