avocado-old/packages/graphics/proton/text-node-renderer.js

180 lines
4.8 KiB
JavaScript
Raw Normal View History

'undefined' !== typeof window ? require('web-animations-js') : '';
2019-04-19 00:50:15 -05:00
import {Proton} from './proton';
import {Vector} from '@avocado/math';
export class TextNode {
constructor(text) {
this.text = text;
}
createDom() {
2019-04-19 00:50:15 -05:00
const div = window.document.createElement('div');
div.className = 'particle';
const span = window.document.createElement('span');
span.className = this.spanClassName();
span.textContent = this.text;
span.style.fontSize = `${this.sizeInPx()}px`;
2019-04-19 00:50:15 -05:00
this.span = span;
div.appendChild(span);
this.div = div;
}
sizeInPx() {
return 12;
}
spanClassName() {
return 'text';
2019-04-19 00:50:15 -05:00
}
particleStateToCssRules(particle) {
const color = particle.color;
// Position.
const transforms = [];
const position = [particle.p.x, particle.p.y];
const scale = particle.scale;
// Offset for font size.
const {style, textContent} = particle.target.span;
const fontSize = this.sizeInPx();
const length = textContent.length;
const offset = (fontSize / 2) * (particle.scale / 2);
position[0] -= offset * length;
position[1] += offset;
transforms.push(`translate(${position[0]}px, ${-position[1]}px)`);
// Angle.
const angle = 360 * (particle.rotation.x / (Math.PI * 2));
transforms.push(`rotate(${angle}deg)`);
// Scale.
transforms.push(`scale(${scale})`);
const rules = {
transform: transforms.join(' '),
};
// Alpha?
2019-04-21 14:58:11 -05:00
if (particle.useAlpha) {
rules.opacity = particle.alpha || 1;
2019-04-21 14:58:11 -05:00
}
// Color?
2019-04-21 14:58:11 -05:00
if (particle.useColor) {
rules.color = `rgb(${color.r * 255}, ${color.g * 255}, ${color.b * 255})`;
2019-04-21 19:24:45 -05:00
}
return rules;
2019-04-19 00:50:15 -05:00
}
}
export class TextNodeRenderer extends Proton.BaseRender {
2019-04-21 14:58:11 -05:00
static get freeList() {
if (!this._freeList) {
this._freeList = [];
}
return this._freeList;
}
static pushToFreeList(target) {
const freeList = this.freeList;
freeList.push(target);
}
static pullFromFreeList() {
const freeList = this.freeList;
return freeList.pop();
}
2019-04-19 00:50:15 -05:00
constructor(selector, stage) {
super();
2019-04-19 23:11:57 -05:00
this._body = new TextNode('');
2019-04-19 00:50:15 -05:00
this.name = 'NodeRenderer';
this.queued = [];
2019-04-20 21:35:07 -05:00
const promise = stage.findUiElement(selector);
promise.then((element) => {
2019-04-22 14:36:39 -05:00
if (this.parent) {
let freeNode;
while (freeNode = this.constructor.pullFromFreeList()) {
this.parent.removeChild(freeNode.div);
2019-04-22 14:36:39 -05:00
}
}
2019-04-20 21:35:07 -05:00
this.parent = element;
// Start with a warm cache.
if (this.parent && 0 === this.constructor.freeList.length) {
for (let i = 0; i < 200; ++i) {
const target = new TextNode();
target.createDom();
this.parent.appendChild(target.div);
target.div.style.opacity = 0;
this.constructor.pushToFreeList(target);
}
}
2019-04-19 00:50:15 -05:00
});
}
onParticleCreated(particle) {
// Clone global body if none was passed.
if (!particle.body) {
particle.body = this._body.clone();
}
// Pull from free list if we can.
const target = this.constructor.pullFromFreeList();
if (target) {
particle.target = target;
particle.target.span.className = particle.body.spanClassName();
particle.target.span.textContent = particle.body.text;
particle.target.span.style.fontSize = `${particle.body.sizeInPx()}px`;
particle.target.div.style.opacity = 1;
2019-04-21 14:58:11 -05:00
}
else {
particle.target = particle.body;
particle.target.createDom();
if (this.parent) {
this.parent.appendChild(particle.target.div);
}
2019-04-19 00:50:15 -05:00
}
// Queue if we don't have a parent yet.
2019-04-19 00:50:15 -05:00
if (!this.parent) {
2019-04-21 14:58:11 -05:00
this.queued.push(particle.target);
2019-04-19 00:50:15 -05:00
}
else {
for (let i = 0; i < this.queued.length; ++i) {
this.parent.appendChild(this.queued[i].div);
2019-04-21 14:58:11 -05:00
}
this.queued = [];
2019-04-19 00:50:15 -05:00
}
// Simulate the particle in discrete steps to build keyframes for
// animation.
const life = particle.life;
particle.life = life * 1.01;
const stepsPerSec = 10;
let stepCount = life * stepsPerSec;
const keyframes = [];
const damping = 1 - .006;
const time = 1 / stepsPerSec;
while (stepCount--) {
const keyframe = particle.target.particleStateToCssRules(
particle,
);
particle.update(time, 0);
Proton.integrator.integrate(particle, time, damping);
keyframes.push(keyframe);
}
particle.life = Infinity;
// Start animation.
const animation = particle.target.div.animate(keyframes, {
duration: life * 1000,
});
animation.addEventListener('finish', () => {
particle.dead = true;
particle.target.div.style.opacity = 0;
this.constructor.pushToFreeList(particle.target);
particle.target = undefined;
});
2019-04-19 00:50:15 -05:00
}
onParticleUpdate(particle) {}
onParticleDead(particle) {}
2019-04-19 00:50:15 -05:00
}