├── example
├── public
│ ├── favicon.ico
│ ├── arial_black.png
│ ├── manifest.json
│ ├── index.html
│ └── arial_black.fnt
├── package.json
└── src
│ └── index.js
├── src
├── .eslintrc
├── elements
│ ├── Graphics.js
│ ├── Rectangle.js
│ ├── TilingSprite.js
│ ├── RootContainer.js
│ ├── Application.js
│ ├── MaskContainer.js
│ ├── BitmapText.js
│ ├── Text.js
│ ├── BackgroundImage.js
│ ├── Container.js
│ ├── NineSliceSprite.js
│ ├── Sprite.js
│ ├── BackgroundContainer.js
│ ├── BaseElement.js
│ ├── ScrollContainer.js
│ ├── TextInput.js
│ └── BitmapTextContainer.js
├── mergeStyles.js
├── Stage.js
├── index.js
└── applyLayoutProperties.js
├── .babelrc
├── .gitignore
├── rollup.config.js
├── README.md
└── package.json
/example/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lunarraid/react-pixi-layout/HEAD/example/public/favicon.ico
--------------------------------------------------------------------------------
/example/public/arial_black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lunarraid/react-pixi-layout/HEAD/example/public/arial_black.png
--------------------------------------------------------------------------------
/src/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "mocha": true
4 | },
5 | "globals": {
6 | "expect": true
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["env", {
4 | "modules": false
5 | }],
6 | "stage-0",
7 | "react"
8 | ],
9 | "plugins": [
10 | "external-helpers"
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/example/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "react-pixi-layout",
3 | "name": "react-pixi-layout",
4 | "start_url": "./index.html",
5 | "display": "standalone",
6 | "theme_color": "#000000",
7 | "background_color": "#ffffff"
8 | }
9 |
--------------------------------------------------------------------------------
/src/elements/Graphics.js:
--------------------------------------------------------------------------------
1 | import { Graphics as PixiGraphics } from 'pixi.js';
2 | import Container from './Container';
3 |
4 | export default class Graphics extends Container {
5 |
6 | createDisplayObject () {
7 | return new PixiGraphics();
8 | }
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # See https://help.github.com/ignore-files/ for more about ignoring files.
3 |
4 | # dependencies
5 | node_modules
6 |
7 | # builds
8 | build
9 | dist
10 |
11 | # misc
12 | .DS_Store
13 | .env
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
--------------------------------------------------------------------------------
/src/mergeStyles.js:
--------------------------------------------------------------------------------
1 | export default function mergeStyles (style) {
2 | if (style === null || typeof style !== 'object') {
3 | return undefined;
4 | }
5 |
6 | if (!Array.isArray(style)) {
7 | return style;
8 | }
9 |
10 | const result = {};
11 | for (let i = 0, styleLength = style.length; i < styleLength; i++) {
12 | const computedStyle = mergeStyles(style[i]);
13 |
14 | if (computedStyle) {
15 | for (const key in computedStyle) {
16 | result[key] = computedStyle[key];
17 | }
18 | }
19 | }
20 |
21 | return result;
22 | }
23 |
--------------------------------------------------------------------------------
/example/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | react-pixi-layout
12 |
13 |
14 |
15 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import babel from 'rollup-plugin-babel';
2 | import nodebuiltins from 'rollup-plugin-node-builtins';
3 | import commonjs from 'rollup-plugin-commonjs';
4 | import external from 'rollup-plugin-peer-deps-external';
5 | import resolve from 'rollup-plugin-node-resolve';
6 |
7 | import pkg from './package.json';
8 |
9 | export default {
10 | external: [ 'typeflex' ],
11 | input: 'src/index.js',
12 | output: [
13 | {
14 | file: pkg.main,
15 | format: 'cjs',
16 | sourcemap: true
17 | },
18 | {
19 | file: pkg.module,
20 | format: 'esm',
21 | sourcemap: true
22 | }
23 | ],
24 | plugins: [
25 | external(),
26 | nodebuiltins(),
27 | babel({ exclude: 'node_modules/**' }),
28 | resolve(),
29 | commonjs()
30 | ]
31 | };
32 |
--------------------------------------------------------------------------------
/src/elements/Rectangle.js:
--------------------------------------------------------------------------------
1 | import TinyColor from '../util/TinyColor';
2 | import Graphics from './Graphics';
3 |
4 | const DEFAULT_COLOR = new TinyColor(0);
5 |
6 | export default class Rectangle extends Graphics {
7 |
8 | _color = DEFAULT_COLOR;
9 |
10 | applyProps (oldProps, newProps) {
11 | super.applyProps(oldProps, newProps);
12 | this._color = this.style.color !== undefined
13 | ? new TinyColor(this.style.color)
14 | : DEFAULT_COLOR;
15 | }
16 |
17 | onLayout (x, y, width, height) {
18 | super.onLayout(x, y, width, height);
19 | const intColor = parseInt('0x' + this._color.toHex(), 16);
20 | this.displayObject.clear();
21 | this.displayObject.beginFill(intColor, this._color.getAlpha());
22 | this.displayObject.drawRect(0, 0, width, height);
23 | this.displayObject.endFill();
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-pixi-layout-example",
3 | "homepage": "https://lunarraid.github.io/react-pixi-layout",
4 | "version": "0.0.0",
5 | "private": true,
6 | "license": "MIT",
7 | "dependencies": {
8 | "pixi.js": "^5.3.0",
9 | "react": "^17.0.0",
10 | "react-dom": "^17.0.0",
11 | "react-pixi-layout": "0.2.1",
12 | "react-scripts": "^4.0.2"
13 | },
14 | "scripts": {
15 | "start": "react-scripts start",
16 | "build": "react-scripts build",
17 | "test": "react-scripts test --env=jsdom",
18 | "eject": "react-scripts eject"
19 | },
20 | "browserslist": {
21 | "production": [
22 | ">0.2%",
23 | "not dead",
24 | "not op_mini all"
25 | ],
26 | "development": [
27 | "last 1 chrome version",
28 | "last 1 firefox version",
29 | "last 1 safari version"
30 | ]
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-pixi-layout
2 |
3 | > React Fiber renderer for pixi.js with flexbox layout
4 |
5 | [](https://www.npmjs.com/package/react-pixi-layout) [](https://standardjs.com)
6 |
7 | ## Example
8 |
9 | (https://codesandbox.io/s/r0lkjp8k1p)
10 |
11 | ## Install
12 |
13 | ```bash
14 | npm install --save react-pixi-layout
15 | ```
16 |
17 | ## Usage
18 |
19 | ```jsx
20 | import React, { Component } from 'react'
21 |
22 | import { Stage, Text } from 'react-pixi-layout'
23 |
24 | class Example extends Component {
25 | render () {
26 | return (
27 |
28 |
29 |
30 | );
31 | }
32 | }
33 | ```
34 |
35 | ## License
36 |
37 | MIT © [lunarraid](https://github.com/lunarraid)
38 |
--------------------------------------------------------------------------------
/src/elements/TilingSprite.js:
--------------------------------------------------------------------------------
1 | import { Texture, TilingSprite as PixiTilingSprite } from 'pixi.js';
2 | import Container from './Container';
3 |
4 | export default class TilingSprite extends Container {
5 |
6 | createDisplayObject () {
7 | return new PixiTilingSprite(Texture.EMPTY);
8 | }
9 |
10 | applyProps (oldProps, newProps) {
11 | super.applyProps(oldProps, newProps);
12 | const texture = newProps.texture ? Texture.from(newProps.texture) : null;
13 | this.displayObject.texture = texture;
14 | this.displayObject.tilePosition.set(newProps.offsetX || 0, newProps.offsetY || 0);
15 | this.displayObject.tileScale.set(newProps.tileScale || 1);
16 | }
17 |
18 | onLayout (x, y, width, height) {
19 | super.onLayout(x, y, width, height);
20 | if (this.displayObject.texture) {
21 | this.displayObject.width = width * this.scaleX;
22 | this.displayObject.height = height * this.scaleY;
23 | }
24 | }
25 |
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-pixi-layout",
3 | "version": "0.5.7",
4 | "description": "React Fiber renderer for pixi.js with flexbox layout",
5 | "author": "lunarraid",
6 | "license": "MIT",
7 | "repository": "lunarraid/react-pixi-layout",
8 | "main": "dist/cjs/index.js",
9 | "module": "dist/esm/index.js",
10 | "scripts": {
11 | "test": "CI=1 react-scripts test --env=jsdom",
12 | "build": "rollup -c",
13 | "start": "rollup -c -w",
14 | "prepare": "npm run build",
15 | "predeploy": "cd example && npm install && npm run build",
16 | "deploy": "gh-pages -d example/build"
17 | },
18 | "dependencies": {
19 | "typeflex": "0.0.1"
20 | },
21 | "peerDependencies": {
22 | "pixi.js": "^6.0.0",
23 | "react": ">=0.17.0 <= 18",
24 | "react-dom": ">=0.17.0 <= 18"
25 | },
26 | "devDependencies": {
27 | "@lunarraid/animated": "^0.2.3",
28 | "babel-core": "^6.26.0",
29 | "babel-eslint": "^10.0.3",
30 | "babel-plugin-external-helpers": "^6.22.0",
31 | "babel-preset-env": "^1.6.0",
32 | "babel-preset-react": "^6.24.1",
33 | "babel-preset-stage-0": "^6.24.1",
34 | "gh-pages": "^2.1.1",
35 | "react-reconciler": "^0.26.2",
36 | "rollup": "^1.32.1",
37 | "rollup-plugin-babel": "^3.0.3",
38 | "rollup-plugin-commonjs": "^10.1.0",
39 | "rollup-plugin-node-builtins": "^2.1.2",
40 | "rollup-plugin-node-resolve": "^5.2.0",
41 | "rollup-plugin-peer-deps-external": "^2.2.3"
42 | },
43 | "files": [
44 | "dist"
45 | ]
46 | }
47 |
--------------------------------------------------------------------------------
/src/elements/RootContainer.js:
--------------------------------------------------------------------------------
1 | import Container from './Container';
2 | import mergeStyles from '../mergeStyles';
3 |
4 | const MAX_LAYOUT_ATTEMPTS = 3;
5 |
6 | export default class RootContainer extends Container {
7 |
8 | constructor (props = {}, root) {
9 | super(props, root);
10 | this.applyProps({}, props);
11 |
12 | this.layoutCallbackViews = [];
13 | this.callbackCount = 0;
14 | this.layoutAttemptCount = 0;
15 | }
16 |
17 | addToCallbackPool (view) {
18 | this.layoutCallbackViews[this.callbackCount] = view;
19 | this.callbackCount++;
20 | }
21 |
22 | removeFromCallbackPool (view) {
23 | const views = this.layoutCallbackViews;
24 |
25 | for (let i = 0; i < this.callbackCount; i++) {
26 | if (views[i] === view) {
27 | views[i] = null;
28 | }
29 | }
30 | }
31 |
32 | updateLayout () {
33 | if (this._layoutDirty) {
34 |
35 | this.layoutAttemptCount++;
36 | this.layoutNode.calculateLayout();
37 | this.applyLayout();
38 |
39 | for (let i = 0; i < this.callbackCount; i++) {
40 | const view = this.layoutCallbackViews[i];
41 |
42 | if (view) {
43 | const { x, y, width, height } = view.cachedLayout;
44 | this.layoutCallbackViews[i].onLayoutCallback(x, y, width, height);
45 | this.layoutCallbackViews[i] = null;
46 | }
47 | }
48 |
49 | this.callbackCount = 0;
50 |
51 | if (this._layoutDirty && this.layoutAttemptCount <= MAX_LAYOUT_ATTEMPTS) {
52 | this.updateLayout();
53 | }
54 |
55 | this.layoutAttemptCount--;
56 | }
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/src/elements/Application.js:
--------------------------------------------------------------------------------
1 | import { Application as PixiApplication } from 'pixi.js';
2 | import RootContainer from './RootContainer';
3 | import mergeStyles from '../mergeStyles';
4 |
5 | const MAX_LAYOUT_ATTEMPTS = 3;
6 |
7 | const optionKeys = [
8 | 'antialias',
9 | 'autoStart',
10 | 'autoResize',
11 | 'backgroundColor',
12 | 'clearBeforeRender',
13 | 'forceCanvas',
14 | 'forceFXAA',
15 | 'height',
16 | 'legacy',
17 | 'powerPreference',
18 | 'preserveDrawingBuffer',
19 | 'resolution',
20 | 'roundPixels',
21 | 'sharedLoader',
22 | 'sharedTicker',
23 | 'transparent',
24 | 'view',
25 | 'width'
26 | ];
27 |
28 | export default class Application extends RootContainer {
29 |
30 | constructor (props, root) {
31 | super(props, root);
32 | window.papp = this;
33 | }
34 |
35 | createDisplayObject (props) {
36 | const options = optionKeys.reduce((result, key) => {
37 | if (props.hasOwnProperty(key)) { result[key] = props[key]; }
38 | return result;
39 | }, {});
40 |
41 | this.view = props.view;
42 | this.application = new PixiApplication(options);
43 | this.application.ticker.add(this.onTick, this);
44 | return this.application.stage;
45 | }
46 |
47 | applyProps (oldProps, newProps) {
48 | const { width, height, style } = newProps;
49 |
50 | if (oldProps.width !== width || oldProps.height !== height) {
51 | this.application.renderer.resize(width, height);
52 | this.view.style.width = '100%';
53 | this.view.style.height = '100%';
54 | this.layoutDirty = true;
55 | }
56 |
57 | const newStyle = { ...mergeStyles(style), width, height };
58 |
59 | super.applyProps(oldProps, { ...newProps, style: newStyle });
60 | }
61 |
62 | onTick () {
63 | this.updateLayout();
64 | }
65 |
66 | destroy () {
67 | this.application.ticker.remove(this.onTick, this);
68 | super.destroy();
69 | }
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/src/Stage.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { PixiContext, ReactPixiLayout } from './index';
3 | import Application from './elements/Application';
4 |
5 | export default class Stage extends React.Component {
6 |
7 | state = {
8 | context: {
9 | application: null
10 | }
11 | };
12 |
13 | componentDidMount () {
14 | const props = { view: this._canvas, ...this.props };
15 |
16 | this._applicationElement = new Application(props);
17 | this._applicationContainer = ReactPixiLayout.createContainer(this._applicationElement);
18 |
19 | this.setState({
20 | context: {
21 | application: this._applicationElement.application,
22 | canvas: this._canvas
23 | }
24 | });
25 |
26 | ReactPixiLayout.injectIntoDevTools({
27 | findFiberByHostInstance: ReactPixiLayout.findFiberByHostInstance,
28 | bundleType: 1,
29 | version: '16.8.6',
30 | rendererPackageName: 'react-pixi-layout'
31 | });
32 |
33 | ReactPixiLayout.updateContainer(this.wrapProvider(), this._applicationContainer, this);
34 | }
35 |
36 | componentDidUpdate (prevProps) {
37 | this._applicationElement.applyProps(prevProps, this.props);
38 |
39 | ReactPixiLayout.updateContainer(this.wrapProvider(), this._applicationContainer, this);
40 | }
41 |
42 | componentWillUnmount () {
43 | ReactPixiLayout.updateContainer(null, this._applicationContainer, this);
44 |
45 | this._applicationElement.destroy();
46 | this._applicationElement = null;
47 | this._applicationContainer = null;
48 |
49 | if (this.props.onApplication) {
50 | this.props.onApplication(null);
51 | }
52 | }
53 |
54 | wrapProvider () {
55 | return (
56 |
57 | { this.props.children }
58 |
59 | );
60 | }
61 |
62 | render () {
63 | return