UNPKG

@babylonjs/gui

Version:

Babylon.js GUI module =====================

1,092 lines (1,091 loc) 64.4 kB
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;