js/Mobilizing/renderer/3D/three/ui/Panel.js
import Clickable from "./Clickable";
import Color from "../types/Color";
import Component from "../../../../core/Component";
import Mesh from "../shape/Mesh";
import Node from "../shape/3D/primitive/Node";
import Plane from "../shape/2D/Plane";
import Texture from "../texture/Texture";
import Vector2 from "../types/Vector2";
import { isObject } from "../../../../core/util/Misc";
import RichText from "../../../../text/RichText";
const OPACITY_ENABLED = 1;
const OPACITY_DISABLED = 0.3;
export default class Panel extends Component {
/**
* Panel UI element is a kind of aggregation between a button, a textured plane and a text label.
* It has a similar interactive behavior as a button.
* @param {Object} params Parameters object, given by the constructor.
* @param {String} [params.title] the title of this panel, a text that will be displayed as a title.
* @param {Camera} [params.camera] the camera to be used for picking.
* @param {String} [params.material="basic"] this panel material type (String) to use for its creation.
* @param {Pointer} [params.pointer] the pointer Object to be used for picking.
* @param {Number} [params.width=50] the panel's width.
* @param {Number} [params.height=50] the panel's height.
* @param {Number} [params.cutOff="auto"] the bevel angle for corners.
* @param {Number} [params.strokeWidth="auto"] the colored stroke around the panel.
* @param {Number} [params.titleHeight="auto"] the title text's height (in pixel)
* @param {Color} [params.fillColor=Color.black] the text's color
* @param {Color} [params.strokeColor=Color.mobilizing] the stroke's color
* @param {Font} [params.font] the panels regular font
* @param {Font} [params.fontItalic] the panels italic font
* @param {Font} [params.fontBold] the panels bold font
* @param {Font} [params.fontBoldItalic] the panels bold-italic font
* @param {Texture} [params.texture] the texture to map on the panel
*/
constructor({
title = null,
camera = null,
material = "basic",
pointer = null,
width = 50,
height = 50,
cutOff = "auto",
strokeWidth = "auto",
titleHeight = "auto",
fillColor = Color.black.clone(),
strokeColor = Color.mobilizing.clone(),
font = null,
fontItalic = null,
fontBold = null,
fontBoldItalic = null,
texture = null
} = {}) {
super(...arguments);
this.title = title;
this.camera = camera;
this.pointer = pointer;
this.material = material;
this.width = width;
this.height = height;
this.cutOff = cutOff;
this.strokeWidth = strokeWidth;
this.titleHeight = titleHeight;
this.fillColor = fillColor;
this.strokeColor = strokeColor;
this.titleHeight = titleHeight;
this.font = font;
this.fontItalic = fontItalic;
this.fontBold = fontBold;
this.fontBoldItalic = fontBoldItalic;
this.texture = texture;
if (this.cutOff === "auto") {
this.cutOff = Math.min(this.width, this.height) / 10;
}
else if (this.cutOff === 0) {
//singularity with cutOff == 0 : titleHeight will be 0 and no text will be displayed
if (this.titleHeight === "auto") {
this.titleHeight = 3;
}
}
if (this.strokeWidth === "auto") {
this.strokeWidth = Math.min(this.width, this.height) / 50;
}
this.titleWidth = this.height - (2 * this.cutOff);
if (this.titleHeight === "auto") {
this.titleHeight = (typeof this.cutOff === "number") ? this.cutOff : (this.width / 10);
}
// Node & Materials
this.root = new Node();
this.transform = this.root.transform;
this.materials = [];
// Vertices
const w = this.width / 2;
const h = this.height / 2;
this.vertices = [
// Top
new Vector2(-w + this.cutOff, h),
new Vector2(w - this.cutOff, h),
// Right
new Vector2(w, h - this.cutOff),
new Vector2(w, -h + this.cutOff),
// Bottom
new Vector2(w - this.cutOff, -h),
new Vector2(-w + this.cutOff, -h),
// Left
new Vector2(-w, -h + this.cutOff),
new Vector2(-w, h - this.cutOff)
];
// Base mesh
this.mesh = new Mesh({ "material": this.material });
this.mesh.generateFillMesh(this.vertices);
//!!trick for panelStack
this.mesh.uiObject = this;
this.root.transform.addChild(this.mesh.transform);
if (title === null && isObject(texture)) {
this.mesh.material.setTexture(texture);
}
else {
this.mesh.material.setColor(this.fillColor);
}
this.mesh.material.setTransparent(true);
this.materials.push(this.mesh.material);
// Stroke
this.strokeMesh = Mesh.generateStrokeMesh(this.mesh, this.strokeWidth);
this.root.transform.addChild(this.strokeMesh.transform);
this.strokeMesh.material.setColor(this.strokeColor);
this.strokeMesh.material.setTransparent(true);
this.materials.push(this.strokeMesh.material);
if (title !== null) {
// Title
const canvasWidth = Math.max(this.titleWidth, 512);
const canvasHeight = canvasWidth * (this.titleHeight / this.titleWidth);
//default font are managed by RichText class
this.titleText = new RichText({
"width": canvasWidth,
"height": canvasHeight,
"marginTop": 0,
"text": `<b>${this.title}</b>`,
"textSize": canvasHeight * 0.75,
"textAlign": "right",
"textColor": this.strokeColor.getStyle(),
"backgroundColor": Color.transparent.makeRGBAStringWithAlpha(0),
"font": this.font,
"fontItalic": this.fontItalic,
"fontBold": this.fontBold,
"fontBoldItalic": this.fontBoldItalic
});
//console.log("this.titleText", this.titleText);
this.titleTexture = new Texture({ canvas: this.titleText.getCanvas() });
//console.log("this.titleTexture", this.titleTexture);
this.texturedMesh = new Plane({
"material": this.material,
"width": this.titleWidth,
"height": this.titleHeight
});
this.texturedMesh.transform.setLocalPosition((this.titleHeight / 2 - this.width / 2), 0, 1);
this.texturedMesh.transform.setLocalRotation(0, 0, 90);
//console.log("this.texturedMesh", this.texturedMesh);
this.root.transform.addChild(this.texturedMesh.transform);
this.texturedMesh.material.setTexture(this.titleTexture);
this.texturedMesh.material.setTransparent(true);
this.materials.push(this.texturedMesh.material);
// Image
if (isObject(this.texture)) {
this.imgMesh = new Plane({
"material": this.material,
"width": this.width - (2 * this.titleHeight),
"height": this.height - (2 * this.titleHeight)
});
this.imgMesh.transform.setLocalPositionZ(0.5);
this.imgMesh.material.setTransparent(true);
this.imgMesh.material.setTexture(texture);
this.root.transform.addChild(this.imgMesh.transform);
this.materials.push(this.imgMesh.material);
}
}
}
setup() {
super.setup();
if (this.context === null) {
throw new Error("Add Panel to a context before calling setup()");
}
// Interactive element
this.clickable = new Clickable({
"camera": this.camera,
"mesh": this.mesh,
"pointer": this.pointer,
"onPress": (pick) => {
if (this.active) {
this.events.trigger("down", pick);
}
},
"onRelease": (pick) => {
if (this.active) {
this.events.trigger("up", pick);
this.events.trigger("click", pick);
}
},
"onEnter": (pick) => {
if (this.active) {
this.events.trigger("over", pick);
}
},
"onLeave": () => {
if (this.active) {
this.events.trigger("out");
}
}
});
this.context.addComponent(this.clickable);
this.clickable.setup();
this.on();
}
/* update() {
} */
setStrokeColor(color) {
this.strokeColor = color;
this.strokeMesh.material.setColor(this.strokeColor);
if (this.titleText) {
this.titleText.textColor = color;
// TODO: Fix text shifting (margin increasing) on each render
this.titleText.lines = [];
this.titleText.render();
}
}
on() {
super.on();
this.materials.forEach((material) => material.setOpacity(OPACITY_ENABLED));
}
off() {
super.off();
this.materials.forEach((material) => material.setOpacity(OPACITY_DISABLED));
}
}