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)
}