import * as I from 'immutable'; import {compose} from '@avocado/core'; import {StateProperty, Trait} from '@avocado/entity'; import {Rectangle, Vector} from '@avocado/math'; import {Property} from '@avocado/mixins'; import {Container} from '../container'; import {hasGraphics} from '../has-graphics'; const AUTO_ZINDEX = 1 << 16; const decorate = compose( StateProperty('isVisible', { track: true, }), Property('visibleBoundingBox', { default: [0, 0, 0, 0], emit: function (...args) { this.entity.emit(...args); }, track: true, }), StateProperty('opacity', { track: true, }), StateProperty('visibleScale', { track: true, }), StateProperty('zIndex', { track: true, }), ); export class Visible extends decorate(Trait) { static defaultParams() { return { trackPosition: true, }; } static defaultState() { return { isVisible: true, opacity: 1, visibleScale: [1, 1], zIndex: AUTO_ZINDEX, }; } initialize() { if (hasGraphics) { this._container = new Container(); this._container.isVisible = this.entity.isVisible; } this.scheduledBoundingBoxUpdate = true; this.trackPosition = this.params.get('trackPosition'); this.onZIndexChanged(); } destroy() { if (this._container) { this._container.destroy(); } } get container() { return this._container; } onZIndexChanged() { const zIndex = this.entity.zIndex; this._usingAutoZIndex = AUTO_ZINDEX === zIndex; if (!this._usingAutoZIndex) { if (this._container) { this._container.zIndex = zIndex; } } } get rawVisibleScale() { const scale = this.entity.visibleScale; return [scale.get(0), scale.get(1)]; } set rawVisibleScale(scale) { this.entity.visibleScale = I.List(scale); } shouldSynchronizePosition() { if (!this._container) { return false; } return this._container && this.trackPosition; } synchronizePosition() { if (!this.entity.is('positioned')) { return; } if (!this._container) { return; } this._container.position = this.entity.position; if (this._usingAutoZIndex) { if (this._container) { this._container.zIndex = this.entity.y; } } } get visibleScaleX() { return this.entity.visibleScale.get(0); } set visibleScaleX(x) { this.rawVisibleScale = [x, this.visibleScaleY]; } get visibleScaleY() { return this.entity.visibleScale.get(1); } set visibleScaleY(y) { this.rawVisibleScale = [this.visibleScaleX, y]; } listeners() { const listeners = { isVisibleChanged: () => { if (!this._container) { return; } this._container.visible = this.entity.isVisible; }, opacityChanged: () => { if (!this._container) { return; } this._container.alpha = this.entity.opacity; }, positionChanged: () => { this.scheduledBoundingBoxUpdate = true; }, zIndexChanged: () => { this.onZIndexChanged(); }, }; if (this.shouldSynchronizePosition()) { listeners.traitAdded = (type) => { if (-1 === [ 'visible', 'positioned', ].indexOf(type)) { return; } this.synchronizePosition(); }; } return listeners; } methods() { return { updateVisibleBoundingBox: () => { this.scheduledBoundingBoxUpdate = true; }, } } tick(elapsed) { if (this.shouldSynchronizePosition()) { this.synchronizePosition(); } if (this.scheduledBoundingBoxUpdate) { // Collect all bounding boxes. const visibleBoundingBoxes = this.entity.invokeHookFlat( 'visibleBoundingBoxes' ); if (0 === visibleBoundingBoxes.length) { this.visibleBoundingBox = [0, 0, 0, 0]; } else { let unifiedBoundingBox = [0, 0, 0, 0]; for (let i = 0; i < visibleBoundingBoxes.length; ++i) { const visibleBoundingBox = visibleBoundingBoxes[i]; unifiedBoundingBox = Rectangle.united( unifiedBoundingBox, visibleBoundingBox, ); } this.visibleBoundingBox = unifiedBoundingBox; this.scheduledBoundingBoxUpdate = false; } } } }