import {compose} from '@avocado/core'; import {StateProperty, Trait} from '@avocado/entity'; import {Rectangle, Vector} from '@avocado/math'; import {Image} from '../image'; import {Sprite} from '../sprite'; const decorate = compose( StateProperty('currentImage', { track: true, }), ); export class Pictured extends decorate(Trait) { static defaultParams() { return { images: {}, }; } static defaultState() { return { currentImage: 'initial', }; } initialize() { this._cachedAabbs = {}; this._images = this.params.get('images').toJS(); this.sprites = undefined; } destroy() { if (this.sprites) { for (const key in this.sprites) { this.hideImage(key); const sprite = this.sprites[key]; sprite.image.destroy(); sprite.destroy(); } } } hideImage(key) { if (!this.sprites) { return; } const sprite = this.sprites[key]; if (!sprite) { return; } this.entity.container.removeChild(sprite); } loadImagesIfPossible() { if (!this.entity.container) { return; } if (this.sprites) { return; } // Load all images. const imagePromises = []; this.sprites = {}; for (const key in this._images) { const {uri} = this._images[key]; const imagePromise = Image.load(uri).then((image) => { const sprite = this.sprites[key] = new Sprite(image); // Calculate any offset. sprite.position = this.offsetFor(key); // Set current image upfront. const isCurrentImage = key === this.entity.currentImage; if (isCurrentImage) { this.showImage(key); } }); imagePromises.push(imagePromise); } Promise.all(imagePromises).then((images) => { // Bounding box update. this.entity.updateVisibleBoundingBox(); this.setSpriteScale(); }); } offsetFor(key) { if (!this._images[key] || !this._images[key].offset) { return [0, 0]; } return this._images[key].offset; } setSpriteScale() { if (!this.sprites) { return; } const rawScale = this.entity.rawVisibleScale; for (const key in this.sprites) { const sprite = this.sprites[key]; sprite.scale = rawScale; } } showImage(key) { if (!this.sprites) { return; } const sprite = this.sprites[key]; if (!sprite) { return; } this.entity.container.addChild(sprite); } sizeFor(key) { if (!this._images[key] || !this._images[key].size) { return [0, 0]; } return this._images[key].size; } hooks() { return { visibleAabbs: () => { const key = this.entity.currentImage; if (this._cachedAabbs[key]) { return this._cachedAabbs[key]; } const image = this._images[key]; if (!image) { return [0, 0, 0, 0]; } const viewPosition = this.offsetFor(key); const size = this.sizeFor(key); const rectangle = Rectangle.compose(viewPosition, size); const expanded = Rectangle.expand( rectangle, Vector.sub(Vector.mul(size, this.entity.rawVisibleScale), size), ); return this._cachedAabbs[key] = expanded; }, } } listeners() { return { currentImageChanged: (oldKey) => { // Bounding box update. this.entity.updateVisibleBoundingBox(); // Only client/graphics. if (!this.sprites) { return; } // Swap the image. this.hideImage(oldKey); this.showImage(this.entity.currentImage); }, visibleScaleChanged: () => { this.setSpriteScale(); }, traitAdded: (type) => { if (-1 === [ 'visible', 'pictured', ].indexOf(type)) { return; } this.loadImagesIfPossible(); }, }; } }