import * as I from 'immutable'; import {compose, Property} from '@avocado/core'; import {StateProperty, Trait} from '@avocado/entity'; import {Rectangle, Vector} from '@avocado/math'; import {Container} from '../container'; import {hasGraphics} from '../has-graphics'; const AUTO_ZINDEX = 1 << 16; const decorate = compose( StateProperty('isVisible', { track: true, }), Property('visibleAabb', { 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 { filter: undefined, trackPosition: true, }; } static defaultState() { return { isVisible: true, opacity: 1, visibleScale: [1, 1], zIndex: AUTO_ZINDEX, }; } static type() { return 'visible'; } initialize() { if (hasGraphics) { this._container = new Container(); const filter = this.params.filter; if (filter) { this._container.setFilter(filter); } this._container.isVisible = this.entity.isVisible; } this._rawVisibleAabb = [0, 0, 0, 0]; this.scheduledBoundingBoxUpdate = true; this.trackPosition = this.params.trackPosition; const scale = this.entity.visibleScale; this._visibleScale = [scale.get(0), scale.get(1)]; 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() { return this._visibleScale; } set rawVisibleScale(scale) { this.entity.visibleScale = I.List(scale); } 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() { return { isVisibleChanged: () => { if (!this._container) { return; } this._container.visible = this.entity.isVisible; }, opacityChanged: () => { if (!this._container) { return; } this._container.alpha = this.entity.opacity; }, positionChanged: () => { this.translateVisibleAabb(); }, traitAdded: (type) => { if (-1 === [ 'visible', 'positioned', ].indexOf(type)) { return; } this.synchronizePosition(); }, visibleScaleChanged: () => { const scale = this.entity.visibleScale; this._visibleScale = [scale.get(0), scale.get(1)]; if (this._container) { this._container.scale = this._visibleScale; } }, zIndexChanged: () => { this.onZIndexChanged(); }, }; } methods() { return { updateVisibleBoundingBox: () => { this.scheduledBoundingBoxUpdate = true; }, } } renderTick(elapsed) { this.synchronizePosition(); } translateVisibleAabb() { this.visibleAabb = Rectangle.translated( this._rawVisibleAabb, this.entity.position ); } tick(elapsed) { if (AVOCADO_SERVER) { if (this.scheduledBoundingBoxUpdate) { // Collect all bounding boxes. const visibleAabbs = this.entity.invokeHookFlat( 'visibleAabbs' ); if (0 === visibleAabbs.length) { this.visibleAabb = [0, 0, 0, 0]; } else { let unifiedBoundingBox = [0, 0, 0, 0]; for (let i = 0; i < visibleAabbs.length; ++i) { const visibleAabb = visibleAabbs[i]; unifiedBoundingBox = Rectangle.united( unifiedBoundingBox, visibleAabb, ); } this._rawVisibleAabb = unifiedBoundingBox; this.translateVisibleAabb(); this.scheduledBoundingBoxUpdate = false; } } } } }