Repository

js/Mobilizing/renderer/3D/three/ui/ButtonGroup.js

import Node from "../shape/3D/primitive/Node";
import Vector3 from "../types/Vector3";
import Component from "../../../../core/Component";

export default class ButtonGroup extends Component {
    /**
    * ButtonGroup organize an array of Buttons with different layout patterns. Make it easier to build menu.
    * @extends {Component}
    * @param {Object} params Parameters object.
    * @param {Number} params.row for grid based layout, the maximum nb of rows
    * @param {Number} params.columns for grid based layout, the maximum nb of columns
    * @param {String} params.mode layout mode. One of grid, honeycomb
    * @param {Array} params.buttons The list of buttons to layout.
    *
    * @example
    *     //TODO
    */
    constructor({
        columns = 2,
        mode = "grid",
        offsetType = "even",
        buttons = [],
    } = {}) {
        super(...arguments);

        this.columns = columns;
        this.mode = mode;
        this.offsetType = offsetType;
        this.buttons = buttons;

        /*
        * index d'élement => position
        * plusieurs implémentations de positionement
        * - grille simple à plusieurs sens
        * - fleur / spirale
        * - clavier
        * - custom
        */

        this.orderedIndices = [];

        this.width = 0;
        this.height = 0;

        this.root = new Node();
        this.transform = this.root.transform;

        if (this.buttons.length > 0) {
            //add objects to this group root
            for (let i = 0; i < this.buttons.length; i++) {
                this.root.transform.addChild(this.buttons[i].transform);
            }

            this.organize(this.mode, this.columns, this.offsetType);
        }
    }

    /*setup()
    {
        super.setup();
        let context = this.getContext();
    }*/

    /*add(button)
    {
        this.buttons.push(button);
        button.setName(this.buttons.length-1);
    }*/

    /**
    * Generic organize method to switch between various layout modes
    * @private
    * @method organize
    * @param {String} mode grid, HoneyComb (for HoneyComb layout)
    */
    organize(mode, columns, offsetType) {

        //console.log(mode, columns, offsetType);

        const indices = this.organizeGrid(columns);

        switch (mode) {
            case "grid":
                this.positionGrid(indices);
                break;
            case "honeycomb": // bee honeycomb!
                this.positionHoneyComb(indices, offsetType);
                break;
            default:
                this.positionGrid(indices);
                break;
        }
    }

    /**
    * Organize the buttons to place in the grid. Each button is associated to an index.
    * @private
    * @method organizeGrid
    * @param {Number} columns
    * @return {Array} indices List of objects like this: {index:i,position: new Vector3(), isTop: false, isBottom: false, isLeft: false, isRight: false}, that will help to compute positions and Mesh deformations
    */
    organizeGrid(columns) {
        const tempIndices = [];

        for (let i = 0; i < this.buttons.length; i++) {
            if (i === 0 || i % columns === 0) {
                tempIndices[tempIndices.length] = [];
            }
            tempIndices[tempIndices.length - 1].push({
                "index": i,
                "position": new Vector3(),
                "isTop": false,
                "isBottom": false,
                "isLeft": false,
                "isRight": false
            });
        }
        return tempIndices;
    }

    /**
    * Compute position and set position flags for each button
    * @private
    * @method positionGrid
    * @param {Array} indices given from organizeGrid
    */
    positionGrid(indices) {
        let index = 0;

        //line nb
        for (let i = 0; i < indices.length; i++) {
            const line = indices[i];

            // column nb
            for (let j = 0; j < line.length; j++) {
                const bt = this.buttons[line[j].index];
                line[j].position.x = j * (bt.width + bt.strokeWidth);
                line[j].position.y = i * -(bt.height + bt.strokeWidth);

                //manage placement by strings
                if (i === 0) {
                    line[j].isTop = true;
                }
                if (i === indices.length - 1) {
                    line[j].isBottom = true;
                }
                if (j === 0) {
                    line[j].isLeft = true;
                }
                if (j === line.length - 1) {
                    line[j].isRight = true;

                    //for corner with no vertical neighbor but a line under
                    if (index + line.length > this.buttons.length - 1) {
                        line[j].isBottom = true;
                    }
                }
                this.orderedIndices.push(line[j]);
                index++;
            }
        }

        this.renderGrid();
    }

    renderGrid() {
        for (let i = 0; i < this.orderedIndices.length; i++) {
            const order = this.orderedIndices[i];
            const bt = this.buttons[i];

            bt.transform.setLocalPosition(order.position);

            //one column singularity
            if (order.isTop && order.isLeft && !order.isBottom && order.isRight) {

                const nextOrder = this.orderedIndices[i + 1];

                if (!nextOrder.isTop && nextOrder.isLeft && !nextOrder.isBottom && nextOrder.isRight) {
                    bt.adaptCorner("straight", "bottomRight");
                    bt.adaptCorner("straight", "bottomLeft");
                }
            }

            //one line only
            if (order.isTop && order.isLeft && order.isBottom && !order.isRight) {
                bt.adaptCorner("straight", "topRight");
                bt.adaptCorner("straight", "bottomRight");
            }
            if (order.isTop && !order.isLeft && order.isBottom && !order.isRight) {
                bt.adaptCorner("straight", "topRight");
                bt.adaptCorner("straight", "bottomRight");
                bt.adaptCorner("straight", "topLeft");
                bt.adaptCorner("straight", "bottomLeft");
            }
            if (order.isTop && !order.isLeft && order.isBottom && order.isRight) {
                bt.adaptCorner("straight", "topLeft");
                bt.adaptCorner("straight", "bottomLeft");
            }
            //several lines
            //middle
            if (order.isTop && !order.isLeft && !order.isBottom && !order.isRight) {
                bt.adaptCorner("straight", "topRight");
                bt.adaptCorner("straight", "bottomRight");
                bt.adaptCorner("straight", "topLeft");
                bt.adaptCorner("straight", "bottomLeft");
            }
            //top
            if (order.isTop && order.isLeft && !order.isBottom && !order.isRight) {
                bt.adaptCorner("straight", "topRight");
                bt.adaptCorner("straight", "bottomRight");
                bt.adaptCorner("straight", "bottomLeft");
            }

            if (order.isTop && !order.isLeft && !order.isBottom && order.isRight) {
                bt.adaptCorner("straight", "topLeft");
                bt.adaptCorner("straight", "bottomLeft");
                bt.adaptCorner("straight", "bottomRight");
            }
            //other middle
            if (!order.isTop && !order.isBottom) {
                bt.adaptCorner("straight", "topRight");
                bt.adaptCorner("straight", "bottomRight");
                bt.adaptCorner("straight", "topLeft");
                bt.adaptCorner("straight", "bottomLeft");
            }
            //bottom
            if (!order.isTop && order.isLeft && order.isBottom && order.isRight) {
                bt.adaptCorner("straight", "topLeft");
                bt.adaptCorner("straight", "topRight");
            }
            if (!order.isTop && order.isLeft && order.isBottom && !order.isRight) {
                bt.adaptCorner("straight", "topLeft");
                bt.adaptCorner("straight", "topRight");
                bt.adaptCorner("straight", "bottomRight");
            }
            if (!order.isTop && !order.isLeft && order.isBottom && order.isRight) {
                bt.adaptCorner("straight", "topLeft");
                bt.adaptCorner("straight", "topRight");
                bt.adaptCorner("straight", "bottomLeft");
            }
            if (!order.isTop && !order.isLeft && order.isBottom && !order.isRight) {
                bt.adaptCorner("straight", "topRight");
                bt.adaptCorner("straight", "bottomRight");
                bt.adaptCorner("straight", "topLeft");
                bt.adaptCorner("straight", "bottomLeft");
            }
        }
        this.computeSize();
    }

    positionHoneyComb(indices, offsetType) {
        //line nb
        for (let i = 0; i < indices.length; i++) {
            const line = indices[i];

            // column nb
            for (let j = 0; j < line.length; j++) {
                const bt = this.buttons[line[j].index];

                /*let halfWidthSquared = Math.pow(bt.mesh.getBoundingBox().getSize().x / 2, 2);
                let twoSquareThree = 2 / Math.sqrt(3);
                let polyHeight = bt.mesh.getBoundingBox().getSize().x * twoSquareThree;
                let interval = Math.sqrt( Math.pow( polyHeight * Math.sin(Math.PI/6), 2) - halfWidthSquared) + polyHeight * Math.sin(Math.PI/6);*/

                const bbox = bt.mesh.getBoundingBox();
                const result = new Vector3();
                const bboxSize = bbox.getSize(result);

                const interval = bboxSize.y * 3 / 4;

                line[j].position.x = j * (bboxSize.x + bt.strokeWidth);
                line[j].position.y = i * -(interval + bt.strokeWidth);

                if (i % 2 === 1) {
                    if (offsetType === "odd") {
                        line[j].position.x -= (bboxSize.x + bt.strokeWidth) / 2;
                    }
                    else {
                        line[j].position.x += (bboxSize.x + bt.strokeWidth) / 2;
                    }
                }
                this.orderedIndices.push(line[j]);
            }
        }
        this.renderHoneyComb();
    }

    renderHoneyComb() {
        for (let i = 0; i < this.orderedIndices.length; i++) {
            const order = this.orderedIndices[i];
            const bt = this.buttons[i];

            bt.transform.setLocalPosition(order.position);
        }
        this.computeSize();
    }

    /**
     * compute the size of this button group by calculation
     */
    computeSize() {

        const bbox = this.buttons[0].mesh.getBoundingBox();

        for (let i = 0; i < this.buttons[0].root.transform.getChildren().length; i++) {
            const object = this.buttons[0].root.transform.getChild(i).getNativeObject();
            bbox.expandByObject(object);
        }

        for (let i = 0; i < this.buttons.length; i++) {

            const vec = this.buttons[i].root.transform.getLocalPosition();
            bbox.expandByPoint(vec);
        }

        const result = new Vector3();
        bbox.getSize(result);

        console.log(this.transform.getLocalScale());
        result.multiply(this.transform.getLocalScale());

        this.width = result.x;
        this.height = result.y;
    }

    //TODO : flower menu with cube coordinates (http://www.redblobgames.com/grids/hexagons)

}