@babylonjs/gui
Version:
Babylon.js GUI module =====================
1,092 lines (1,091 loc) • 64.4 kB
JavaScript
import { __decorate } from "@babylonjs/core/tslib.es6.js";
import { Observable } from "@babylonjs/core/Misc/observable.js";
import { Control } from "./control.js";
import { InputText } from "./inputText.js";
import { Rectangle } from "./rectangle.js";
import { Button } from "./button.js";
import { Grid } from "./grid.js";
import { TextBlock } from "../controls/textBlock.js";
import { RegisterClass } from "@babylonjs/core/Misc/typeStore.js";
import { Color3 } from "@babylonjs/core/Maths/math.color.js";
import { serialize } from "@babylonjs/core/Misc/decorators.js";
import { EngineStore } from "@babylonjs/core/Engines/engineStore.js";
/** Class used to create color pickers */
export class ColorPicker extends Control {
/** Gets or sets the color of the color picker */
get value() {
return this._value;
}
set value(value) {
if (this._value.equals(value)) {
return;
}
this._value.copyFrom(value);
this._value.toHSVToRef(this._tmpColor);
this._h = this._tmpColor.r;
this._s = Math.max(this._tmpColor.g, 0.00001);
this._v = Math.max(this._tmpColor.b, 0.00001);
this._markAsDirty();
if (this._value.r <= ColorPicker._Epsilon) {
this._value.r = 0;
}
if (this._value.g <= ColorPicker._Epsilon) {
this._value.g = 0;
}
if (this._value.b <= ColorPicker._Epsilon) {
this._value.b = 0;
}
if (this._value.r >= 1.0 - ColorPicker._Epsilon) {
this._value.r = 1.0;
}
if (this._value.g >= 1.0 - ColorPicker._Epsilon) {
this._value.g = 1.0;
}
if (this._value.b >= 1.0 - ColorPicker._Epsilon) {
this._value.b = 1.0;
}
this.onValueChangedObservable.notifyObservers(this._value);
}
/**
* Gets or sets control width
* @see https://doc.babylonjs.com/features/featuresDeepDive/gui/gui#position-and-size
*/
get width() {
return this._width.toString(this._host);
}
set width(value) {
if (this._width.toString(this._host) === value) {
return;
}
if (this._width.fromString(value)) {
if (this._width.getValue(this._host) === 0) {
value = "1px";
this._width.fromString(value);
}
this._height.fromString(value);
this._markAsDirty();
}
}
/**
* Gets or sets control height
* @see https://doc.babylonjs.com/features/featuresDeepDive/gui/gui#position-and-size
*/
get height() {
return this._height.toString(this._host);
}
/** Gets or sets control height */
set height(value) {
if (this._height.toString(this._host) === value) {
return;
}
if (this._height.fromString(value)) {
if (this._height.getValue(this._host) === 0) {
value = "1px";
this._height.fromString(value);
}
this._width.fromString(value);
this._markAsDirty();
}
}
/** Gets or sets control size */
get size() {
return this.width;
}
set size(value) {
this.width = value;
}
/**
* Creates a new ColorPicker
* @param name defines the control name
*/
constructor(name) {
super(name);
this.name = name;
this._value = Color3.Red();
this._tmpColor = new Color3();
this._pointerStartedOnSquare = false;
this._pointerStartedOnWheel = false;
this._squareLeft = 0;
this._squareTop = 0;
this._squareSize = 0;
this._h = 360;
this._s = 1;
this._v = 1;
this._lastPointerDownId = -1;
/**
* Observable raised when the value changes
*/
this.onValueChangedObservable = new Observable();
// Events
this._pointerIsDown = false;
this.value = new Color3(0.88, 0.1, 0.1);
this.size = "200px";
this.isPointerBlocker = true;
}
_getTypeName() {
return "ColorPicker";
}
/**
* @internal
*/
_preMeasure(parentMeasure) {
if (parentMeasure.width < parentMeasure.height) {
this._currentMeasure.height = parentMeasure.width;
}
else {
this._currentMeasure.width = parentMeasure.height;
}
}
_updateSquareProps() {
const radius = Math.min(this._currentMeasure.width, this._currentMeasure.height) * 0.5;
const wheelThickness = radius * 0.2;
const innerDiameter = (radius - wheelThickness) * 2;
const squareSize = innerDiameter / Math.sqrt(2);
const offset = radius - squareSize * 0.5;
this._squareLeft = this._currentMeasure.left + offset;
this._squareTop = this._currentMeasure.top + offset;
this._squareSize = squareSize;
}
_drawGradientSquare(hueValue, left, top, width, height, context) {
const lgh = context.createLinearGradient(left, top, width + left, top);
lgh.addColorStop(0, "#fff");
lgh.addColorStop(1, "hsl(" + hueValue + ", 100%, 50%)");
context.fillStyle = lgh;
context.fillRect(left, top, width, height);
const lgv = context.createLinearGradient(left, top, left, height + top);
lgv.addColorStop(0, "rgba(0,0,0,0)");
lgv.addColorStop(1, "#000");
context.fillStyle = lgv;
context.fillRect(left, top, width, height);
}
_drawCircle(centerX, centerY, radius, context) {
context.beginPath();
context.arc(centerX, centerY, radius + 1, 0, 2 * Math.PI, false);
context.lineWidth = 3;
context.strokeStyle = "#333333";
context.stroke();
context.beginPath();
context.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
context.lineWidth = 3;
context.strokeStyle = "#ffffff";
context.stroke();
}
_createColorWheelCanvas(radius, thickness) {
// Shoudl abstract platform instead of using LastCreatedEngine
const engine = EngineStore.LastCreatedEngine;
if (!engine) {
throw new Error("Invalid engine. Unable to create a canvas.");
}
const canvas = engine.createCanvas(radius * 2, radius * 2);
const context = canvas.getContext("2d");
const image = context.getImageData(0, 0, radius * 2, radius * 2);
const data = image.data;
const color = this._tmpColor;
const maxDistSq = radius * radius;
const innerRadius = radius - thickness;
const minDistSq = innerRadius * innerRadius;
for (let x = -radius; x < radius; x++) {
for (let y = -radius; y < radius; y++) {
const distSq = x * x + y * y;
if (distSq > maxDistSq || distSq < minDistSq) {
continue;
}
const dist = Math.sqrt(distSq);
const ang = Math.atan2(y, x);
Color3.HSVtoRGBToRef((ang * 180) / Math.PI + 180, dist / radius, 1, color);
const index = (x + radius + (y + radius) * 2 * radius) * 4;
data[index] = color.r * 255;
data[index + 1] = color.g * 255;
data[index + 2] = color.b * 255;
let alphaRatio = (dist - innerRadius) / (radius - innerRadius);
//apply less alpha to bigger color pickers
let alphaAmount = 0.2;
const maxAlpha = 0.2;
const minAlpha = 0.04;
const lowerRadius = 50;
const upperRadius = 150;
if (radius < lowerRadius) {
alphaAmount = maxAlpha;
}
else if (radius > upperRadius) {
alphaAmount = minAlpha;
}
else {
alphaAmount = ((minAlpha - maxAlpha) * (radius - lowerRadius)) / (upperRadius - lowerRadius) + maxAlpha;
}
alphaRatio = (dist - innerRadius) / (radius - innerRadius);
if (alphaRatio < alphaAmount) {
data[index + 3] = 255 * (alphaRatio / alphaAmount);
}
else if (alphaRatio > 1 - alphaAmount) {
data[index + 3] = 255 * (1.0 - (alphaRatio - (1 - alphaAmount)) / alphaAmount);
}
else {
data[index + 3] = 255;
}
}
}
context.putImageData(image, 0, 0);
return canvas;
}
/**
* @internal
*/
_draw(context) {
context.save();
this._applyStates(context);
const radius = Math.min(this._currentMeasure.width, this._currentMeasure.height) * 0.5;
const wheelThickness = radius * 0.2;
const left = this._currentMeasure.left;
const top = this._currentMeasure.top;
if (!this._colorWheelCanvas || this._colorWheelCanvas.width != radius * 2) {
this._colorWheelCanvas = this._createColorWheelCanvas(radius, wheelThickness);
}
this._updateSquareProps();
if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {
context.shadowColor = this.shadowColor;
context.shadowBlur = this.shadowBlur;
context.shadowOffsetX = this.shadowOffsetX;
context.shadowOffsetY = this.shadowOffsetY;
context.fillRect(this._squareLeft, this._squareTop, this._squareSize, this._squareSize);
}
context.drawImage(this._colorWheelCanvas, left, top);
if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {
context.shadowBlur = 0;
context.shadowOffsetX = 0;
context.shadowOffsetY = 0;
}
this._drawGradientSquare(this._h, this._squareLeft, this._squareTop, this._squareSize, this._squareSize, context);
let cx = this._squareLeft + this._squareSize * this._s;
let cy = this._squareTop + this._squareSize * (1 - this._v);
this._drawCircle(cx, cy, radius * 0.04, context);
const dist = radius - wheelThickness * 0.5;
cx = left + radius + Math.cos(((this._h - 180) * Math.PI) / 180) * dist;
cy = top + radius + Math.sin(((this._h - 180) * Math.PI) / 180) * dist;
this._drawCircle(cx, cy, wheelThickness * 0.35, context);
context.restore();
}
_updateValueFromPointer(x, y) {
if (this._pointerStartedOnWheel) {
const radius = Math.min(this._currentMeasure.width, this._currentMeasure.height) * 0.5;
const centerX = radius + this._currentMeasure.left;
const centerY = radius + this._currentMeasure.top;
this._h = (Math.atan2(y - centerY, x - centerX) * 180) / Math.PI + 180;
}
else if (this._pointerStartedOnSquare) {
this._updateSquareProps();
this._s = (x - this._squareLeft) / this._squareSize;
this._v = 1 - (y - this._squareTop) / this._squareSize;
this._s = Math.min(this._s, 1);
this._s = Math.max(this._s, ColorPicker._Epsilon);
this._v = Math.min(this._v, 1);
this._v = Math.max(this._v, ColorPicker._Epsilon);
}
Color3.HSVtoRGBToRef(this._h, this._s, this._v, this._tmpColor);
this.value = this._tmpColor;
}
_isPointOnSquare(x, y) {
this._updateSquareProps();
const left = this._squareLeft;
const top = this._squareTop;
const size = this._squareSize;
if (x >= left && x <= left + size && y >= top && y <= top + size) {
return true;
}
return false;
}
_isPointOnWheel(x, y) {
const radius = Math.min(this._currentMeasure.width, this._currentMeasure.height) * 0.5;
const centerX = radius + this._currentMeasure.left;
const centerY = radius + this._currentMeasure.top;
const wheelThickness = radius * 0.2;
const innerRadius = radius - wheelThickness;
const radiusSq = radius * radius;
const innerRadiusSq = innerRadius * innerRadius;
const dx = x - centerX;
const dy = y - centerY;
const distSq = dx * dx + dy * dy;
if (distSq <= radiusSq && distSq >= innerRadiusSq) {
return true;
}
return false;
}
_onPointerDown(target, coordinates, pointerId, buttonIndex, pi) {
if (!super._onPointerDown(target, coordinates, pointerId, buttonIndex, pi)) {
return false;
}
if (this.isReadOnly) {
return true;
}
this._pointerIsDown = true;
this._pointerStartedOnSquare = false;
this._pointerStartedOnWheel = false;
// Invert transform
this._invertTransformMatrix.transformCoordinates(coordinates.x, coordinates.y, this._transformedPosition);
const x = this._transformedPosition.x;
const y = this._transformedPosition.y;
if (this._isPointOnSquare(x, y)) {
this._pointerStartedOnSquare = true;
}
else if (this._isPointOnWheel(x, y)) {
this._pointerStartedOnWheel = true;
}
this._updateValueFromPointer(x, y);
this._host._capturingControl[pointerId] = this;
this._lastPointerDownId = pointerId;
return true;
}
_onPointerMove(target, coordinates, pointerId, pi) {
// Only listen to pointer move events coming from the last pointer to click on the element (To support dual vr controller interaction)
if (pointerId != this._lastPointerDownId) {
return;
}
if (!this.isReadOnly) {
// Invert transform
this._invertTransformMatrix.transformCoordinates(coordinates.x, coordinates.y, this._transformedPosition);
const x = this._transformedPosition.x;
const y = this._transformedPosition.y;
if (this._pointerIsDown) {
this._updateValueFromPointer(x, y);
}
}
super._onPointerMove(target, coordinates, pointerId, pi);
}
_onPointerUp(target, coordinates, pointerId, buttonIndex, notifyClick, pi) {
this._pointerIsDown = false;
delete this._host._capturingControl[pointerId];
super._onPointerUp(target, coordinates, pointerId, buttonIndex, notifyClick, pi);
}
_onCanvasBlur() {
this._forcePointerUp();
super._onCanvasBlur();
}
/**
* This function expands the color picker by creating a color picker dialog with manual
* color value input and the ability to save colors into an array to be used later in
* subsequent launches of the dialogue.
* @param advancedTexture defines the AdvancedDynamicTexture the dialog is assigned to
* @param options defines size for dialog and options for saved colors. Also accepts last color picked as hex string and saved colors array as hex strings.
* @param options.pickerWidth
* @param options.pickerHeight
* @param options.headerHeight
* @param options.lastColor
* @param options.swatchLimit
* @param options.numSwatchesPerLine
* @param options.savedColors
* @returns picked color as a hex string and the saved colors array as hex strings.
*/
static async ShowPickerDialogAsync(advancedTexture, options) {
return await new Promise((resolve) => {
// Default options
options.pickerWidth = options.pickerWidth || "640px";
options.pickerHeight = options.pickerHeight || "400px";
options.headerHeight = options.headerHeight || "35px";
options.lastColor = options.lastColor || "#000000";
options.swatchLimit = options.swatchLimit || 20;
options.numSwatchesPerLine = options.numSwatchesPerLine || 10;
// Window size settings
const drawerMaxRows = options.swatchLimit / options.numSwatchesPerLine;
const rawSwatchSize = parseFloat(options.pickerWidth) / options.numSwatchesPerLine;
const gutterSize = Math.floor(rawSwatchSize * 0.25);
const colGutters = gutterSize * (options.numSwatchesPerLine + 1);
const swatchSize = Math.floor((parseFloat(options.pickerWidth) - colGutters) / options.numSwatchesPerLine);
const drawerMaxSize = swatchSize * drawerMaxRows + gutterSize * (drawerMaxRows + 1);
const containerSize = (parseInt(options.pickerHeight) + drawerMaxSize + Math.floor(swatchSize * 0.25)).toString() + "px";
// Button Colors
const buttonColor = "#c0c0c0";
const buttonBackgroundColor = "#535353";
const buttonBackgroundHoverColor = "#414141";
const buttonBackgroundClickColor = "515151";
const buttonDisabledColor = "#555555";
const buttonDisabledBackgroundColor = "#454545";
const currentSwatchesOutlineColor = "#404040";
const luminanceLimitColor = Color3.FromHexString("#dddddd");
const luminanceLimit = luminanceLimitColor.r + luminanceLimitColor.g + luminanceLimitColor.b;
const iconColorDark = "#aaaaaa";
const iconColorLight = "#ffffff";
// Button settings
let buttonFontSize;
let butEdit;
// Input Text Colors
const inputFieldLabels = ["R", "G", "B"];
const inputTextBackgroundColor = "#454545";
const inputTextColor = "#f0f0f0";
// This int is used for naming swatches and serves as the index for calling them from the list
let swatchNumber;
// Menu Panel options. We need to know if the swatchDrawer exists so we can create it if needed.
let swatchDrawer;
let editSwatchMode = false;
// Color InputText fields that will be updated upon value change
let butSave;
let lastVal;
let activeField;
// Dialog menu container which will contain both the main dialogue window and the swatch drawer which opens once a color is saved.
const dialogContainer = new Grid();
dialogContainer.name = "Dialog Container";
dialogContainer.width = options.pickerWidth;
if (options.savedColors) {
dialogContainer.height = containerSize;
const topRow = parseInt(options.pickerHeight) / parseInt(containerSize);
dialogContainer.addRowDefinition(topRow, false);
dialogContainer.addRowDefinition(1.0 - topRow, false);
}
else {
dialogContainer.height = options.pickerHeight;
dialogContainer.addRowDefinition(1.0, false);
}
advancedTexture.addControl(dialogContainer);
// Swatch drawer which contains all saved color buttons
if (options.savedColors) {
swatchDrawer = new Grid();
swatchDrawer.name = "Swatch Drawer";
swatchDrawer.verticalAlignment = Control.VERTICAL_ALIGNMENT_TOP;
swatchDrawer.background = buttonBackgroundColor;
swatchDrawer.width = options.pickerWidth;
const initialRows = options.savedColors.length / options.numSwatchesPerLine;
let gutterCount;
if (initialRows == 0) {
gutterCount = 0;
}
else {
gutterCount = initialRows + 1;
}
swatchDrawer.height = (swatchSize * initialRows + gutterCount * gutterSize).toString() + "px";
swatchDrawer.top = Math.floor(swatchSize * 0.25).toString() + "px";
for (let i = 0; i < Math.ceil(options.savedColors.length / options.numSwatchesPerLine) * 2 + 1; i++) {
if (i % 2 != 0) {
swatchDrawer.addRowDefinition(swatchSize, true);
}
else {
swatchDrawer.addRowDefinition(gutterSize, true);
}
}
for (let i = 0; i < options.numSwatchesPerLine * 2 + 1; i++) {
if (i % 2 != 0) {
swatchDrawer.addColumnDefinition(swatchSize, true);
}
else {
swatchDrawer.addColumnDefinition(gutterSize, true);
}
}
dialogContainer.addControl(swatchDrawer, 1, 0);
}
// Picker container
const pickerPanel = new Grid();
pickerPanel.name = "Picker Panel";
pickerPanel.height = options.pickerHeight;
const panelHead = parseInt(options.headerHeight) / parseInt(options.pickerHeight);
const pickerPanelRows = [panelHead, 1.0 - panelHead];
pickerPanel.addRowDefinition(pickerPanelRows[0], false);
pickerPanel.addRowDefinition(pickerPanelRows[1], false);
dialogContainer.addControl(pickerPanel, 0, 0);
// Picker container header
const header = new Rectangle();
header.name = "Dialogue Header Bar";
header.background = "#cccccc";
header.thickness = 0;
pickerPanel.addControl(header, 0, 0);
// Header close button
const closeButton = Button.CreateSimpleButton("closeButton", "a");
closeButton.fontFamily = "coreglyphs";
const headerColor3 = Color3.FromHexString(header.background);
const closeIconColor = new Color3(1.0 - headerColor3.r, 1.0 - headerColor3.g, 1.0 - headerColor3.b);
closeButton.color = closeIconColor.toHexString();
closeButton.fontSize = Math.floor(parseInt(options.headerHeight) * 0.6);
closeButton.textBlock.textVerticalAlignment = Control.VERTICAL_ALIGNMENT_CENTER;
closeButton.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_RIGHT;
closeButton.height = closeButton.width = options.headerHeight;
closeButton.background = header.background;
closeButton.thickness = 0;
closeButton.pointerDownAnimation = () => { };
closeButton.pointerUpAnimation = () => {
closeButton.background = header.background;
};
closeButton.pointerEnterAnimation = () => {
closeButton.color = header.background;
closeButton.background = "red";
};
closeButton.pointerOutAnimation = () => {
closeButton.color = closeIconColor.toHexString();
closeButton.background = header.background;
};
closeButton.onPointerClickObservable.add(() => {
closePicker(currentSwatch.background);
});
pickerPanel.addControl(closeButton, 0, 0);
// Dialog container body
const dialogBody = new Grid();
dialogBody.name = "Dialogue Body";
dialogBody.background = buttonBackgroundColor;
const dialogBodyCols = [0.4375, 0.5625];
dialogBody.addRowDefinition(1.0, false);
dialogBody.addColumnDefinition(dialogBodyCols[0], false);
dialogBody.addColumnDefinition(dialogBodyCols[1], false);
pickerPanel.addControl(dialogBody, 1, 0);
// Picker grid
const pickerGrid = new Grid();
pickerGrid.name = "Picker Grid";
pickerGrid.addRowDefinition(0.85, false);
pickerGrid.addRowDefinition(0.15, false);
dialogBody.addControl(pickerGrid, 0, 0);
// Picker control
const picker = new ColorPicker();
picker.name = "GUI Color Picker";
if (options.pickerHeight < options.pickerWidth) {
picker.width = 0.89;
}
else {
picker.height = 0.89;
}
picker.value = Color3.FromHexString(options.lastColor);
picker.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_CENTER;
picker.verticalAlignment = Control.VERTICAL_ALIGNMENT_CENTER;
picker.onPointerDownObservable.add(() => {
activeField = picker.name;
lastVal = "";
editSwatches(false);
});
picker.onValueChangedObservable.add(function (value) {
// value is a color3
if (activeField == picker.name) {
updateValues(value, picker.name);
}
});
pickerGrid.addControl(picker, 0, 0);
// Picker body right quarant
const pickerBodyRight = new Grid();
pickerBodyRight.name = "Dialogue Right Half";
pickerBodyRight.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT;
const pickerBodyRightRows = [0.514, 0.486];
pickerBodyRight.addRowDefinition(pickerBodyRightRows[0], false);
pickerBodyRight.addRowDefinition(pickerBodyRightRows[1], false);
dialogBody.addControl(pickerBodyRight, 1, 1);
// Picker container swatches and buttons
const pickerSwatchesButtons = new Grid();
pickerSwatchesButtons.name = "Swatches and Buttons";
const pickerButtonsCol = [0.417, 0.583];
pickerSwatchesButtons.addRowDefinition(1.0, false);
pickerSwatchesButtons.addColumnDefinition(pickerButtonsCol[0], false);
pickerSwatchesButtons.addColumnDefinition(pickerButtonsCol[1], false);
pickerBodyRight.addControl(pickerSwatchesButtons, 0, 0);
// Picker Swatches quadrant
const pickerSwatches = new Grid();
pickerSwatches.name = "New and Current Swatches";
const pickeSwatchesRows = [0.04, 0.16, 0.64, 0.16];
pickerSwatches.addRowDefinition(pickeSwatchesRows[0], false);
pickerSwatches.addRowDefinition(pickeSwatchesRows[1], false);
pickerSwatches.addRowDefinition(pickeSwatchesRows[2], false);
pickerSwatches.addRowDefinition(pickeSwatchesRows[3], false);
pickerSwatchesButtons.addControl(pickerSwatches, 0, 0);
// Active swatches
const activeSwatches = new Grid();
activeSwatches.name = "Active Swatches";
activeSwatches.width = 0.67;
activeSwatches.addRowDefinition(0.5, false);
activeSwatches.addRowDefinition(0.5, false);
pickerSwatches.addControl(activeSwatches, 2, 0);
const labelWidth = Math.floor(parseInt(options.pickerWidth) * dialogBodyCols[1] * pickerButtonsCol[0] * 0.11);
const labelHeight = Math.floor(parseInt(options.pickerHeight) * pickerPanelRows[1] * pickerBodyRightRows[0] * pickeSwatchesRows[1] * 0.5);
let labelTextSize;
if (options.pickerWidth > options.pickerHeight) {
labelTextSize = labelHeight;
}
else {
labelTextSize = labelWidth;
}
// New color swatch and previous color button
const newText = new TextBlock();
newText.text = "new";
newText.name = "New Color Label";
newText.color = buttonColor;
newText.fontSize = labelTextSize;
pickerSwatches.addControl(newText, 1, 0);
const newSwatch = new Rectangle();
newSwatch.name = "New Color Swatch";
newSwatch.background = options.lastColor;
newSwatch.thickness = 0;
activeSwatches.addControl(newSwatch, 0, 0);
const currentSwatch = Button.CreateSimpleButton("currentSwatch", "");
currentSwatch.background = options.lastColor;
currentSwatch.thickness = 0;
currentSwatch.onPointerClickObservable.add(() => {
const revertColor = Color3.FromHexString(currentSwatch.background);
updateValues(revertColor, currentSwatch.name);
editSwatches(false);
});
currentSwatch.pointerDownAnimation = () => { };
currentSwatch.pointerUpAnimation = () => { };
currentSwatch.pointerEnterAnimation = () => { };
currentSwatch.pointerOutAnimation = () => { };
activeSwatches.addControl(currentSwatch, 1, 0);
const swatchOutline = new Rectangle();
swatchOutline.name = "Swatch Outline";
swatchOutline.width = 0.67;
swatchOutline.thickness = 2;
swatchOutline.color = currentSwatchesOutlineColor;
swatchOutline.isHitTestVisible = false;
pickerSwatches.addControl(swatchOutline, 2, 0);
const currentText = new TextBlock();
currentText.name = "Current Color Label";
currentText.text = "current";
currentText.color = buttonColor;
currentText.fontSize = labelTextSize;
pickerSwatches.addControl(currentText, 3, 0);
// Buttons grid
const buttonGrid = new Grid();
buttonGrid.name = "Button Grid";
buttonGrid.height = 0.8;
const buttonGridRows = 1 / 3;
buttonGrid.addRowDefinition(buttonGridRows, false);
buttonGrid.addRowDefinition(buttonGridRows, false);
buttonGrid.addRowDefinition(buttonGridRows, false);
pickerSwatchesButtons.addControl(buttonGrid, 0, 1);
// Determine pixel width and height for all buttons from overall panel dimensions
const buttonWidth = Math.floor(parseInt(options.pickerWidth) * dialogBodyCols[1] * pickerButtonsCol[1] * 0.67).toString() + "px";
const buttonHeight = Math.floor(parseInt(options.pickerHeight) * pickerPanelRows[1] * pickerBodyRightRows[0] * (parseFloat(buttonGrid.height.toString()) / 100) * buttonGridRows * 0.7).toString() + "px";
// Determine button type size
if (parseFloat(buttonWidth) > parseFloat(buttonHeight)) {
buttonFontSize = Math.floor(parseFloat(buttonHeight) * 0.45);
}
else {
buttonFontSize = Math.floor(parseFloat(buttonWidth) * 0.11);
}
// Panel Buttons
const butOK = Button.CreateSimpleButton("butOK", "OK");
butOK.width = buttonWidth;
butOK.height = buttonHeight;
butOK.verticalAlignment = Control.VERTICAL_ALIGNMENT_CENTER;
butOK.thickness = 2;
butOK.color = buttonColor;
butOK.fontSize = buttonFontSize;
butOK.background = buttonBackgroundColor;
butOK.onPointerEnterObservable.add(() => {
butOK.background = buttonBackgroundHoverColor;
});
butOK.onPointerOutObservable.add(() => {
butOK.background = buttonBackgroundColor;
});
butOK.pointerDownAnimation = () => {
butOK.background = buttonBackgroundClickColor;
};
butOK.pointerUpAnimation = () => {
butOK.background = buttonBackgroundHoverColor;
};
butOK.onPointerClickObservable.add(() => {
editSwatches(false);
closePicker(newSwatch.background);
});
buttonGrid.addControl(butOK, 0, 0);
const butCancel = Button.CreateSimpleButton("butCancel", "Cancel");
butCancel.width = buttonWidth;
butCancel.height = buttonHeight;
butCancel.verticalAlignment = Control.VERTICAL_ALIGNMENT_CENTER;
butCancel.thickness = 2;
butCancel.color = buttonColor;
butCancel.fontSize = buttonFontSize;
butCancel.background = buttonBackgroundColor;
butCancel.onPointerEnterObservable.add(() => {
butCancel.background = buttonBackgroundHoverColor;
});
butCancel.onPointerOutObservable.add(() => {
butCancel.background = buttonBackgroundColor;
});
butCancel.pointerDownAnimation = () => {
butCancel.background = buttonBackgroundClickColor;
};
butCancel.pointerUpAnimation = () => {
butCancel.background = buttonBackgroundHoverColor;
};
butCancel.onPointerClickObservable.add(() => {
editSwatches(false);
closePicker(currentSwatch.background);
});
buttonGrid.addControl(butCancel, 1, 0);
if (options.savedColors) {
butSave = Button.CreateSimpleButton("butSave", "Save");
butSave.width = buttonWidth;
butSave.height = buttonHeight;
butSave.verticalAlignment = Control.VERTICAL_ALIGNMENT_CENTER;
butSave.thickness = 2;
butSave.fontSize = buttonFontSize;
if (options.savedColors.length < options.swatchLimit) {
butSave.color = buttonColor;
butSave.background = buttonBackgroundColor;
}
else {
disableButton(butSave, true);
}
butSave.onPointerEnterObservable.add(() => {
if (options.savedColors) {
if (options.savedColors.length < options.swatchLimit) {
butSave.background = buttonBackgroundHoverColor;
}
}
});
butSave.onPointerOutObservable.add(() => {
if (options.savedColors) {
if (options.savedColors.length < options.swatchLimit) {
butSave.background = buttonBackgroundColor;
}
}
});
butSave.pointerDownAnimation = () => {
if (options.savedColors) {
if (options.savedColors.length < options.swatchLimit) {
butSave.background = buttonBackgroundClickColor;
}
}
};
butSave.pointerUpAnimation = () => {
if (options.savedColors) {
if (options.savedColors.length < options.swatchLimit) {
butSave.background = buttonBackgroundHoverColor;
}
}
};
butSave.onPointerClickObservable.add(() => {
if (options.savedColors) {
if (options.savedColors.length == 0) {
setEditButtonVisibility(true);
}
if (options.savedColors.length < options.swatchLimit) {
updateSwatches(newSwatch.background, butSave);
}
editSwatches(false);
}
});
if (options.savedColors.length > 0) {
setEditButtonVisibility(true);
}
buttonGrid.addControl(butSave, 2, 0);
}
// Picker color values input
const pickerColorValues = new Grid();
pickerColorValues.name = "Dialog Lower Right";
pickerColorValues.addRowDefinition(0.02, false);
pickerColorValues.addRowDefinition(0.63, false);
pickerColorValues.addRowDefinition(0.21, false);
pickerColorValues.addRowDefinition(0.14, false);
pickerBodyRight.addControl(pickerColorValues, 1, 0);
// RGB values text boxes
const currentColor = Color3.FromHexString(options.lastColor);
const rgbValuesQuadrant = new Grid();
rgbValuesQuadrant.name = "RGB Values";
rgbValuesQuadrant.width = 0.82;
rgbValuesQuadrant.verticalAlignment = Control.VERTICAL_ALIGNMENT_CENTER;
rgbValuesQuadrant.addRowDefinition(1 / 3, false);
rgbValuesQuadrant.addRowDefinition(1 / 3, false);
rgbValuesQuadrant.addRowDefinition(1 / 3, false);
rgbValuesQuadrant.addColumnDefinition(0.1, false);
rgbValuesQuadrant.addColumnDefinition(0.2, false);
rgbValuesQuadrant.addColumnDefinition(0.7, false);
pickerColorValues.addControl(rgbValuesQuadrant, 1, 0);
for (let i = 0; i < inputFieldLabels.length; i++) {
const labelText = new TextBlock();
labelText.text = inputFieldLabels[i];
labelText.color = buttonColor;
labelText.fontSize = buttonFontSize;
rgbValuesQuadrant.addControl(labelText, i, 0);
}
// Input fields for RGB values
const rValInt = new InputText();
rValInt.width = 0.83;
rValInt.height = 0.72;
rValInt.name = "rIntField";
rValInt.fontSize = buttonFontSize;
rValInt.text = (currentColor.r * 255).toString();
rValInt.color = inputTextColor;
rValInt.background = inputTextBackgroundColor;
rValInt.onFocusObservable.add(() => {
activeField = rValInt.name;
lastVal = rValInt.text;
editSwatches(false);
});
rValInt.onBlurObservable.add(() => {
if (rValInt.text == "") {
rValInt.text = "0";
}
updateInt(rValInt, "r");
if (activeField == rValInt.name) {
activeField = "";
}
});
rValInt.onTextChangedObservable.add(() => {
if (activeField == rValInt.name) {
updateInt(rValInt, "r");
}
});
rgbValuesQuadrant.addControl(rValInt, 0, 1);
const gValInt = new InputText();
gValInt.width = 0.83;
gValInt.height = 0.72;
gValInt.name = "gIntField";
gValInt.fontSize = buttonFontSize;
gValInt.text = (currentColor.g * 255).toString();
gValInt.color = inputTextColor;
gValInt.background = inputTextBackgroundColor;
gValInt.onFocusObservable.add(() => {
activeField = gValInt.name;
lastVal = gValInt.text;
editSwatches(false);
});
gValInt.onBlurObservable.add(() => {
if (gValInt.text == "") {
gValInt.text = "0";
}
updateInt(gValInt, "g");
if (activeField == gValInt.name) {
activeField = "";
}
});
gValInt.onTextChangedObservable.add(() => {
if (activeField == gValInt.name) {
updateInt(gValInt, "g");
}
});
rgbValuesQuadrant.addControl(gValInt, 1, 1);
const bValInt = new InputText();
bValInt.width = 0.83;
bValInt.height = 0.72;
bValInt.name = "bIntField";
bValInt.fontSize = buttonFontSize;
bValInt.text = (currentColor.b * 255).toString();
bValInt.color = inputTextColor;
bValInt.background = inputTextBackgroundColor;
bValInt.onFocusObservable.add(() => {
activeField = bValInt.name;
lastVal = bValInt.text;
editSwatches(false);
});
bValInt.onBlurObservable.add(() => {
if (bValInt.text == "") {
bValInt.text = "0";
}
updateInt(bValInt, "b");
if (activeField == bValInt.name) {
activeField = "";
}
});
bValInt.onTextChangedObservable.add(() => {
if (activeField == bValInt.name) {
updateInt(bValInt, "b");
}
});
rgbValuesQuadrant.addControl(bValInt, 2, 1);
const rValDec = new InputText();
rValDec.width = 0.95;
rValDec.height = 0.72;
rValDec.name = "rDecField";
rValDec.fontSize = buttonFontSize;
rValDec.text = currentColor.r.toString();
rValDec.color = inputTextColor;
rValDec.background = inputTextBackgroundColor;
rValDec.onFocusObservable.add(() => {
activeField = rValDec.name;
lastVal = rValDec.text;
editSwatches(false);
});
rValDec.onBlurObservable.add(() => {
if (parseFloat(rValDec.text) == 0 || rValDec.text == "") {
rValDec.text = "0";
updateFloat(rValDec, "r");
}
if (activeField == rValDec.name) {
activeField = "";
}
});
rValDec.onTextChangedObservable.add(() => {
if (activeField == rValDec.name) {
updateFloat(rValDec, "r");
}
});
rgbValuesQuadrant.addControl(rValDec, 0, 2);
const gValDec = new InputText();
gValDec.width = 0.95;
gValDec.height = 0.72;
gValDec.name = "gDecField";
gValDec.fontSize = buttonFontSize;
gValDec.text = currentColor.g.toString();
gValDec.color = inputTextColor;
gValDec.background = inputTextBackgroundColor;
gValDec.onFocusObservable.add(() => {
activeField = gValDec.name;
lastVal = gValDec.text;
editSwatches(false);
});
gValDec.onBlurObservable.add(() => {
if (parseFloat(gValDec.text) == 0 || gValDec.text == "") {
gValDec.text = "0";
updateFloat(gValDec, "g");
}
if (activeField == gValDec.name) {
activeField = "";
}
});
gValDec.onTextChangedObservable.add(() => {
if (activeField == gValDec.name) {
updateFloat(gValDec, "g");
}
});
rgbValuesQuadrant.addControl(gValDec, 1, 2);
const bValDec = new InputText();
bValDec.width = 0.95;
bValDec.height = 0.72;
bValDec.name = "bDecField";
bValDec.fontSize = buttonFontSize;
bValDec.text = currentColor.b.toString();
bValDec.color = inputTextColor;
bValDec.background = inputTextBackgroundColor;
bValDec.onFocusObservable.add(() => {
activeField = bValDec.name;
lastVal = bValDec.text;
editSwatches(false);
});
bValDec.onBlurObservable.add(() => {
if (parseFloat(bValDec.text) == 0 || bValDec.text == "") {
bValDec.text = "0";
updateFloat(bValDec, "b");
}
if (activeField == bValDec.name) {
activeField = "";
}
});
bValDec.onTextChangedObservable.add(() => {
if (activeField == bValDec.name) {
updateFloat(bValDec, "b");
}
});
rgbValuesQuadrant.addControl(bValDec, 2, 2);
// Hex value input
const hexValueQuadrant = new Grid();
hexValueQuadrant.name = "Hex Value";
hexValueQuadrant.width = 0.82;
hexValueQuadrant.addRowDefinition(1.0, false);
hexValueQuadrant.addColumnDefinition(0.1, false);
hexValueQuadrant.addColumnDefinition(0.9, false);
pickerColorValues.addControl(hexValueQuadrant, 2, 0);
const labelText = new TextBlock();
labelText.text = "#";
labelText.color = buttonColor;
labelText.fontSize = buttonFontSize;
hexValueQuadrant.addControl(labelText, 0, 0);
const hexVal = new InputText();
hexVal.width = 0.96;
hexVal.height = 0.72;
hexVal.name = "hexField";
hexVal.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_CENTER;
hexVal.fontSize = buttonFontSize;
const minusPound = options.lastColor.split("#");
hexVal.text = minusPound[1];
hexVal.color = inputTextColor;
hexVal.background = inputTextBackgroundColor;
hexVal.onFocusObservable.add(() => {
activeField = hexVal.name;
lastVal = hexVal.text;
editSwatches(false);
});
hexVal.onBlurObservable.add(() => {
if (hexVal.text.length == 3) {
const val = hexVal.text.split("");
hexVal.text = val[0] + val[0] + val[1] + val[1] + val[2] + val[2];
}
if (hexVal.text == "") {
hexVal.text = "000000";
updateValues(Color3.FromHexString(hexVal.text), "b");
}
if (activeField == hexVal.name) {
activeField = "";
}
});
hexVal.onTextChangedObservable.add(() => {
let newHexValue = hexVal.text;
const checkHex = /[^0-9A-F]/i.test(newHexValue);
if ((hexVal.text.length > 6 || checkHex) && activeField == hexVal.name) {
hexVal.text = lastVal;
}
else {
if (hexVal.text.length < 6) {
const leadingZero = 6 - hexVal.text.length;
for (let i = 0; i < leadingZero; i++) {
newHexValue = "0" + newHexValue;
}
}
if (hexVal.text.length == 3) {
const val = hexVal.text.split("");
newHexValue = val[0] + val[0] + val[1] + val[1] + val[2] + val[2];
}
newHexValue = "#" + newHexValue;
if (activeField == hexVal.name) {
lastVal = hexVal.text;
updateValues(Color3.FromHexString(newHexValue), hexVal.name);
}
}
});
hexValueQuadrant.addControl(hexVal, 0, 1);
if (options.savedColors && options.savedColors.length > 0) {
updateSwatches("", butSave);
}
/**
* Will update all values for InputText and ColorPicker controls based on the BABYLON.Color3 passed to this function.
* Each InputText control and the ColorPicker control will be tested to see if they are the activeField and if they
* are will receive no update. This is to prevent the input from the user being overwritten.
* @param value
* @param inputField
*/
function updateValues(value, inputField) {
activeField = inputField;
const pickedColor = value.toHexString();
newSwatch.background = pickedColor;
if (rValInt.name != activeField) {
rValInt.text = Math.floor(value.r * 255).toString();
}
if (gValInt.name != activeField) {
gValInt.text = Math.floor(value.g * 255).toString();
}
if (bValInt.name != activeField) {
bValInt.text = Math.floor(value.b * 255).toString();
}
if (rValDec.name != activeField) {
rValDec.text = value.r.toString();
}
if (gValDec.name != activeField) {
gValDec.text = value.g.toString();
}
if (bValDec.name != activeField) {
bValDec.text = value.b.toString();
}
if (hexVal.name != activeField) {
const minusPound = pickedColor.split("#");
hexVal.text = minusPound[1];
}
if (picker.name != activeField) {
picker.value = value;
}
}
// When the user enters an integer for R, G, or B we check to make sure it is a valid number and replace if not.
function updateInt(field, channel) {
let newValue = field.text;
const checkVal = /[^0-9]/g.test(newValue);
if (checkVal) {
field.text = lastVal;
return;
}
else {
if (newValue != "") {
if (Math.floor(parseInt(newValue)) < 0) {
newValue = "0";
}
else if (Math.floor(parseInt(newValue)) > 255) {
newValue = "255";
}
else if (isNaN(parseInt(newValue))) {
newValue = "0";
}
}
if (activeField == field.name) {
lastVal = newValue;
}
}
if (newValue != "") {
newValue = parseInt(newValue).toString();
field.text = newValue;