avocado-old/packages/graphics/stage.js

266 lines
7.4 KiB
JavaScript
Raw Normal View History

2019-04-18 22:08:47 -05:00
import {compose} from '@avocado/core';
2019-03-30 05:08:33 -05:00
import {Vector} from '@avocado/math';
2019-04-18 22:08:47 -05:00
import {Property} from '@avocado/mixins';
2019-03-30 05:08:33 -05:00
import {Container} from './container';
import {Renderer} from './renderer';
2019-04-18 22:08:47 -05:00
const decorate = compose(
Property('camera', {
track: true,
}),
);
export class Stage extends decorate(Container) {
2019-03-30 05:08:33 -05:00
2019-04-19 19:58:39 -05:00
constructor(visibleSize, visibleScale) {
2019-03-30 05:08:33 -05:00
super();
2019-04-19 19:58:39 -05:00
const size = Vector.mul(visibleSize, visibleScale);
// Container element.
this.element = window.document.createElement('div');
this.element.className = 'avocado-stage';
this.element.style.height = '100%';
this.element.style.lineHeight = '0';
2019-04-13 21:37:53 -05:00
this.element.style.position = 'relative';
this.element.style.width = '100%';
// DOM parent.
2019-03-30 05:08:33 -05:00
this.parent = undefined;
2019-04-20 02:15:04 -05:00
// Set scale.
this.scale = visibleScale;
// Canvas renderer.
2019-03-30 05:08:33 -05:00
this.renderer = new Renderer(size);
this.renderer.element.style.width = '100%';
this.renderer.element.style.height = '100%';
// "real" dimensions.
2019-03-30 05:08:33 -05:00
this.size = size;
// Precalc for position/dimension transformation.
this._transformRatio = 1;
// UI DOM node.
this.ui = this.createUiLayer();
2019-04-20 21:33:12 -05:00
this._queuedFindSelectors = [];
// Event handlers.
2019-03-30 05:08:33 -05:00
this.onWindowResize = this.onWindowResize.bind(this);
window.addEventListener('resize', this.onWindowResize);
this.onPointerDown = this.onPointerDown.bind(this);
for (const start of ['pointerdown', 'touchstart']) {
this.element.addEventListener(start, this.onPointerDown);
2019-03-30 05:08:33 -05:00
}
this.onPointerMove = this.onPointerMove.bind(this);
for (const move of ['pointermove', 'touchmove']) {
this.element.addEventListener(move, this.onPointerMove);
2019-03-30 05:08:33 -05:00
}
this.onPointerUp = this.onPointerUp.bind(this);
for (const end of ['pointerup', 'touchend']) {
this.element.addEventListener(end, this.onPointerUp);
2019-03-30 05:08:33 -05:00
}
// Put the renderer and UI in the container element, and mark it
// focusable.
this.element.appendChild(this.renderer.element);
this.element.appendChild(this.ui);
this.element.tabIndex = 0;
2019-03-30 05:08:33 -05:00
}
addToDom(parent) {
// Had a parent? Remove.
2019-04-20 21:33:12 -05:00
this.removeFromDom();
2019-03-30 05:08:33 -05:00
this.parent = parent;
// Add to new parent (if any) and focus.
if (parent) {
parent.appendChild(this.element);
}
// Recalculate size and ratio.
2019-03-30 05:08:33 -05:00
this.onWindowResize();
}
createUiLayer() {
const node = window.document.createElement('div');
node.className = 'ui';
2019-04-23 03:21:53 -05:00
node.style.overflow = 'hidden';
node.style.position = 'absolute';
2019-04-20 02:15:04 -05:00
node.style.width = `${this.size[0] / this.scale[0]}px`;
node.style.height = `${this.size[1] / this.scale[1]}px`;
2019-04-18 20:44:50 -05:00
node.style.left = 0;
node.style.top = 0;
node.style.transformOrigin = '0 0 0';
return node;
2019-03-30 05:08:33 -05:00
}
destroy() {
window.removeEventListener('resize', this.onWindowResize);
this.renderer.destroy();
super.destroy();
if (this.parent) {
this.parent.removeChild(this.renderer.element);
}
for (const start of ['pointerdown', 'touchstart']) {
this.element.removeEventListener(start, this.onPointerDown);
2019-03-30 05:08:33 -05:00
}
for (const move of ['pointermove', 'touchmove']) {
this.element.removeEventListener(move, this.onPointerMove);
2019-03-30 05:08:33 -05:00
}
for (const end of ['pointerup', 'touchend']) {
this.element.removeEventListener(end, this.onPointerUp);
2019-03-30 05:08:33 -05:00
}
}
2019-04-14 00:21:47 -05:00
get displaySize() {
return [
this.element.style.width,
this.element.style.height,
];
}
2019-04-20 21:33:12 -05:00
findUiElement(selector) {
const node = this.ui.querySelector(selector);
if (node) {
return Promise.resolve(node);
}
let resolve;
const promise = new Promise((resolve_) => resolve = resolve_);
this._queuedFindSelectors.push({resolve, selector});
return promise
}
flushUiElements() {
for (let i = 0; i < this._queuedFindSelectors.length; i++) {
const {resolve, selector} = this._queuedFindSelectors[i];
resolve(this.ui.querySelector(selector));
}
this._queuedFindSelectors = [];
}
2019-04-19 22:29:05 -05:00
focus() {
this.element.focus();
}
2019-03-30 05:08:33 -05:00
onPointerDown(event) {
const stageEvent = this.stageEventFromNativeEvent(event);
2019-03-30 05:08:33 -05:00
this.emit('pointerDown', stageEvent);
}
onPointerMove(event) {
const stageEvent = this.stageEventFromNativeEvent(event);
2019-03-30 05:08:33 -05:00
this.emit('pointerMove', stageEvent);
}
onPointerUp(event) {
const stageEvent = this.stageEventFromNativeEvent(event);
2019-03-30 05:08:33 -05:00
this.emit('pointerUp', stageEvent);
}
onWindowResize() {
if (!this.parent) {
return;
}
// Find the biggest axe, width or height.
const ratio = this.parent.clientWidth / this.parent.clientHeight;
const biggest = ratio > (16 / 9) ? 'height' : 'width';
// Key parent client size by axe.
const parentClient = {
width: this.parent.clientWidth,
height: this.parent.clientHeight,
};
for (const key of ['height', 'width']) {
// Biggest axe? Inherit parent axe size.
if (key === biggest) {
this.element.style[key] = `${parentClient[biggest]}px`;
}
// Otherwise,
else {
// Derive height from width.
if ('width' === biggest) {
this.element.style.height = `${parentClient[biggest] * 9 / 16}px`;
}
// Derive width from height.
else {
this.element.style.width = `${parentClient[biggest] * 16 / 9}px`;
}
2019-03-30 05:08:33 -05:00
}
}
// Precalc the transformation ratio and apply it to the UI layer.
this._transformRatio = this.size[0] / this.element.clientWidth;
2019-04-20 02:15:04 -05:00
const scaleFactor = 1 / this._transformRatio;
const scaleX = scaleFactor * this.scale[0];
const scaleY = scaleFactor * this.scale[1];
this.ui.style.transform = `scaleX(${scaleX}) scaleY(${scaleY})`;
this.emit('displaySizeChanged', this.displaySize);
2019-03-30 05:08:33 -05:00
}
2019-04-20 21:33:12 -05:00
removeFromDom() {
if (this.parent) {
this.parent.removeChild(this.element);
this.parent = undefined;
}
}
2019-04-23 16:56:47 -05:00
renderTick(elapsed) {
2019-04-19 00:21:24 -05:00
this.tick(0);
2019-03-30 05:08:33 -05:00
this.renderer.render(this);
2019-04-23 16:56:47 -05:00
if (this.camera) {
const inverseOffset = Vector.mul(
this.camera.realOffset,
Vector.scale(this.scale, -1),
);
this.position = inverseOffset;
}
2019-03-30 05:08:33 -05:00
}
2019-04-18 23:58:26 -05:00
resolveUiRendered() {
this._uiResolve();
}
stageEventFromNativeEvent(event) {
event = event || window.event;
const position = this.transformEventPosition(event);
const stageEvent = new StageEvent();
stageEvent._position = position;
stageEvent._target = event.target;
return stageEvent;
}
2019-04-04 19:42:11 -05:00
transformCanvasPosition(position) {
const rect = this.renderer.element.getBoundingClientRect();
const topLeft = [rect.x, rect.y];
const offset = Vector.sub(position, topLeft);
return Vector.div(
Vector.scale(offset, this._transformRatio),
this.scale,
);
}
transformEventPosition(event) {
2019-03-30 05:08:33 -05:00
let position;
if (window.PointerEvent && event instanceof window.PointerEvent) {
2019-03-30 05:08:33 -05:00
position = [event.clientX, event.clientY];
}
else if (window.TouchEvent && event instanceof window.TouchEvent) {
2019-03-30 05:08:33 -05:00
const touches = event.changedTouches;
const touch = touches[0];
position = [touch.clientX, touch.clientY];
}
return this.transformCanvasPosition(position);
2019-03-30 05:08:33 -05:00
}
2019-04-14 00:22:36 -05:00
get transformRatio() {
return this._transformRatio;
}
2019-03-30 05:08:33 -05:00
}
export class StageEvent {
constructor() {
this._position = [-1, -1];
this._target = undefined;
}
2019-03-30 05:08:33 -05:00
get position() {
return this._position;
}
get target() {
return this._target;
2019-03-30 05:08:33 -05:00
}
}