From f22ea42b54fb54b0b952ee5cd6dcd350a009831f Mon Sep 17 00:00:00 2001 From: cha0s Date: Mon, 18 Mar 2019 20:06:47 -0500 Subject: [PATCH] feat: graphics --- packages/graphics/color.js | 50 ++++++++++++++++++++++++ packages/graphics/container.js | 37 ++++++++++++++++++ packages/graphics/image.js | 56 +++++++++++++++++++++++++++ packages/graphics/index.js | 7 ++++ packages/graphics/package.json | 4 +- packages/graphics/primitives.js | 67 +++++++++++++++++++++++++++++++++ packages/graphics/renderable.js | 57 ++++++++++++++++++++++++++++ packages/graphics/renderer.js | 45 ++++++++++++++++++++++ packages/graphics/sprite.js | 26 +++++++++++++ 9 files changed, 348 insertions(+), 1 deletion(-) create mode 100644 packages/graphics/color.js create mode 100644 packages/graphics/container.js create mode 100644 packages/graphics/primitives.js create mode 100644 packages/graphics/renderable.js create mode 100644 packages/graphics/renderer.js create mode 100644 packages/graphics/sprite.js diff --git a/packages/graphics/color.js b/packages/graphics/color.js new file mode 100644 index 0000000..4170925 --- /dev/null +++ b/packages/graphics/color.js @@ -0,0 +1,50 @@ +import {compose} from '@avocado/core'; + +import {Property} from '@avocado/mixins'; + +const decorate = compose( + Property('red'), + Property('green'), + Property('blue'), + Property('alpha'), +) + +export class Color { + + static fromCss(css) { + if ('#'.charCodeAt(0) === css.charCodeAt(0)) { + let hex = css.substr(1); + if (3 === hex.length) { + hex = hex.split('').map((c) => c + c).join(''); + } + const red = parseInt(hex.substr(0, 2), 16); + const green = parseInt(hex.substr(2, 2), 16); + const blue = parseInt(hex.substr(4, 2), 16); + return new Color(red, green, blue); + } + else { + const colors = css.replace( + /\s/g, '' + ).match( + /rgba?\((.*)\)/ + )[1].split(','); + return new Color(colors[0], colors[1], colors[2], colors[3] || 1); + } + } + + constructor(red = 255, green = 0, blue = 255, alpha = 1) { + this.red = red; + this.green = green; + this.blue = blue; + this.alpha = alpha; + } + + toCss() { + return `rgba(${this.red}, ${this.green}, ${this.blue}, ${this.alpha})`; + } + + toInteger() { + return (this.red << 16) | (this.green << 8) | this.blue; + } + +} \ No newline at end of file diff --git a/packages/graphics/container.js b/packages/graphics/container.js new file mode 100644 index 0000000..b944b45 --- /dev/null +++ b/packages/graphics/container.js @@ -0,0 +1,37 @@ +const PIXI = 'undefined' !== typeof window ? require('pixi.js') : undefined; + +import {Renderable} from './renderable'; + +export class Container extends Renderable { + + constructor() { + super(); + this._children = []; + this.internal = PIXI ? new PIXI.Container() : undefined; + } + + addChild(child) { + this._children.push(child); + this.internal.addChild(child.internal); + } + + children() { + return this._children; + } + + removeChild(child) { + const index = this._children.indexOf(child); + if (-1 === index) { + return; + } + this._children.splice(index, 1); + this.internal.removeChild(child.internal); + } + + removeAllChildren() { + for (const child of this._children) { + this.removeChild(child); + } + } + +} diff --git a/packages/graphics/image.js b/packages/graphics/image.js index e69de29..e91aca7 100644 --- a/packages/graphics/image.js +++ b/packages/graphics/image.js @@ -0,0 +1,56 @@ +const PIXI = 'undefined' !== typeof window ? require('pixi.js') : undefined; + +import {Resource} from '@avocado/resource'; + +const baseTextureCache = {}; + +export class Image extends Resource { + + constructor() { + super(); + this.texture = null; + } + + static load(uri) { + return this.loadBaseTexture(uri).then((baseTexture) => { + const image = new Image(); + image.uri = uri; + image.texture = new PIXI.Texture(baseTexture); + return image; + }); + } + + static loadBaseTexture(uri) { + if (baseTextureCache[uri]) { + return Promise.resolve(baseTextureCache[uri]); + } + return new Promise((resolve, reject) => { + const baseTexture = PIXI.BaseTexture.fromImage(uri); + baseTexture.on('error', () => { + reject(new Error(`Couldn't load image "${uri}"`)); + }); + baseTexture.on('loaded', () => { + resolve(baseTextureCache[uri] = baseTexture); + }); + }); + } + + get height() { + if (!this.texture) { + return 0; + } + return this.texture.height; + } + + get size() { + return [this.width, this.height]; + } + + get width() { + if (!this.texture) { + return 0; + } + return this.texture.width; + } + +} diff --git a/packages/graphics/index.js b/packages/graphics/index.js index e69de29..3837017 100644 --- a/packages/graphics/index.js +++ b/packages/graphics/index.js @@ -0,0 +1,7 @@ +export {Color} from './color'; +export {Container} from './container'; +export {Image} from './image'; +export {Primitives} from './primitives'; +export {Renderable} from './renderable'; +export {Renderer} from './renderer'; +export {Sprite} from './sprite'; diff --git a/packages/graphics/package.json b/packages/graphics/package.json index f918968..0e76f2f 100644 --- a/packages/graphics/package.json +++ b/packages/graphics/package.json @@ -8,7 +8,9 @@ "@avocado/core": "1.x", "@avocado/math": "1.x", "@avocado/mixins": "1.x", + "@avocado/resource": "1.x", "debug": "^3.1.0", - "immutable": "4.0.0-rc.12" + "immutable": "4.0.0-rc.12", + "pixi.js": "4.8.6" } } diff --git a/packages/graphics/primitives.js b/packages/graphics/primitives.js new file mode 100644 index 0000000..5f4279c --- /dev/null +++ b/packages/graphics/primitives.js @@ -0,0 +1,67 @@ +const PIXI = 'undefined' !== typeof window ? require('pixi.js') : undefined; + +import {Color} from './color'; +import {Renderable} from './renderable'; + +export class Primitives extends Renderable { + + static fillStyle(color) { + return {color}; + } + + static lineStyle(color, thickness = 1) { + return {color, thickness}; + } + + constructor() { + super(); + this.internal = new PIXI.Graphics(); + } + + clear() { + this.internal.clear(); + } + + drawCircle(position, radius, lineStyle, fillStyle) { + this._wrapStyle('drawCircle', '3rd', lineStyle, fillStyle, () => { + this.internal.drawCircle(position[0], position[1], radius); + }); + } + + drawLine(p1, p2, lineStyle, fillStyle) { + this._wrapStyle('drawLine', '3rd', lineStyle, fillStyle, () => { + this.internal.moveTo(p1[0], p1[1]); + this.internal.lineTo(p2[0], p2[1]); + }); + } + + drawRectangle(rectangle, lineStyle, fillStyle) { + this._wrapStyle('drawLine', '2nd', lineStyle, fillStyle, () => { + this.internal.drawRect( + rectangle[0], + rectangle[1], + rectangle[2], + rectangle[3] + ); + }); + } + + _wrapStyle(method, where, lineStyle, fillStyle, fn) { + if (!lineStyle) { + throw new TypeError( + `Primitives::${method} expects lineStyle as ${where} parameter` + ); + } + if (fillStyle) { + const {color} = fillStyle; + this.internal.beginFill(color.toInteger(), color.alpha) + } + const {color, thickness} = lineStyle; + this.internal.lineStyle(thickness, color.toInteger(), color.alpha) + fn(); + if (fillStyle) { + this.internal.endFill(); + } + } + +} diff --git a/packages/graphics/renderable.js b/packages/graphics/renderable.js new file mode 100644 index 0000000..c8eb4b0 --- /dev/null +++ b/packages/graphics/renderable.js @@ -0,0 +1,57 @@ +export class Renderable { + + get alpha() { + return this.internal.alpha; + } + + set alpha(alpha) { + this.internal.alpha = alpha; + } + + get isValid() { + return !!this.internal; + } + + get position() { + return [this.internal.x, this.internal.y]; + } + + set position(position) { + this.internal.x = position[0]; + this.internal.y = position[1]; + } + + get rotation() { + return this.internal.rotation; + } + + set rotation(rotation) { + this.internal.rotation = rotation; + } + + get visible() { + return this.internal.visible; + } + + set visible(isVisible) { + this.internal.visible = isVisible; + } + + get x() { + return this.internal.x; + } + + set x(x) { + this.internal.x = x; + } + + get y() { + return this.internal.y; + } + + set y(y) { + this.internal.y = y; + } + +} + diff --git a/packages/graphics/renderer.js b/packages/graphics/renderer.js new file mode 100644 index 0000000..c0ab409 --- /dev/null +++ b/packages/graphics/renderer.js @@ -0,0 +1,45 @@ +const PIXI = 'undefined' !== typeof window ? require('pixi.js') : undefined; + +export class Renderer { + + constructor(size, type = 'auto') { + switch (type) { + case 'auto': + this.renderer = PIXI.autoDetectRenderer(size[0], size[1]); + break; + case 'canvas': + this.renderer = new PIXI.CanvasRenderer(size[0], size[1]); + break; + case 'auto': + this.renderer = new PIXI.Renderer(size[0], size[1]); + break; + } + } + + destroy() { + this.renderer.destroy(); + } + + get element() { + return this.renderer.view; + } + + get height() { + return this.element.height; + } + + render(item) { + this.renderer.render(item.internal); + } + + get size() { + return [this.width, this.height]; + } + + get width() { + return this.element.width; + } + + + +} diff --git a/packages/graphics/sprite.js b/packages/graphics/sprite.js new file mode 100644 index 0000000..011a485 --- /dev/null +++ b/packages/graphics/sprite.js @@ -0,0 +1,26 @@ +const PIXI = 'undefined' !== typeof window ? require('pixi.js') : undefined; + +import {Renderable} from './renderable'; + +export class Sprite extends Renderable { + + constructor(image) { + super(); + this._image = image; + this.internal = new PIXI.Sprite(image.texture); + } + + get image() { + return this._image; + } + + setSourceRectangle(rectangle) { + this._image.texture.frame = { + x: rectangle[0], + y: rectangle[1], + width: rectangle[2], + height: rectangle[3], + }; + } + +}