Browse Source

Fix color picker

David Catuhe 4 years ago
parent
commit
aff7222fbe

+ 1 - 1
inspector/src/components/actionTabs/lines/color3LineComponent.tsx

@@ -145,7 +145,7 @@ export class Color3LineComponent extends React.Component<IColor3LineComponentPro
                         {this.props.label}
                     </div>
                     <div className="color3">
-                        <ColorPickerLineComponent value={this.state.color} disableAlpha={true} onColorChanged={color => {
+                        <ColorPickerLineComponent value={this.state.color} onColorChanged={color => {
                             this.onChange(color);
                         }} />                             
                     </div>

+ 19 - 43
inspector/src/components/actionTabs/lines/colorPickerComponent.tsx

@@ -1,38 +1,29 @@
 import * as React from "react";
 import { Color4, Color3 } from 'babylonjs/Maths/math.color';
-import { SketchPicker } from 'react-color';
+import { ColorPicker } from '../../controls/colorPicker/colorPicker';
 
 export interface IColorPickerComponentProps {
     value: Color4 | Color3;
     onColorChanged: (newOne: string) => void;
-    disableAlpha?: boolean;
 }
 
 interface IColorPickerComponentState {
     pickerEnabled: boolean;
-    color: {
-        r: number,
-        g: number,
-        b: number,
-        a?: number
-    },
-    hex: string
+    color: Color3 | Color4;
+    hex: string;
 }
 
 export class ColorPickerLineComponent extends React.Component<IColorPickerComponentProps, IColorPickerComponentState> {
-    private _floatRef: React.RefObject<HTMLDivElement>
-    private _floatHostRef: React.RefObject<HTMLDivElement>
+    private _floatRef: React.RefObject<HTMLDivElement>;
+    private _floatHostRef: React.RefObject<HTMLDivElement>;
 
     constructor(props: IColorPickerComponentProps) {
         super(props);
 
-        this.state = {pickerEnabled: false, color: {
-            r: this.props.value.r * 255,
-            g: this.props.value.g * 255,
-            b: this.props.value.b * 255,
-            a: this.props.value instanceof Color4 ? this.props.value.a * 100 : 100,
-        }, hex: this.props.value.toHexString()};
-        
+        this.state = {pickerEnabled: false,
+            color: this.props.value,
+            hex: this.props.value.toHexString()};
+
         this._floatRef = React.createRef();
         this._floatHostRef = React.createRef();
     }
@@ -52,7 +43,7 @@ export class ColorPickerLineComponent extends React.Component<IColorPickerCompon
             top = window.innerHeight - height - 10;
         }
 
-        div.style.top = top + "px";        
+        div.style.top = top + "px";
         div.style.left = host.getBoundingClientRect().left - div.getBoundingClientRect().width + "px";
     }
 
@@ -60,17 +51,11 @@ export class ColorPickerLineComponent extends React.Component<IColorPickerCompon
         let diffProps = nextProps.value.toHexString() !== this.props.value.toHexString();
 
         if (diffProps) {
-            nextState.color =  {
-                r: nextProps.value.r * 255,
-                g: nextProps.value.g * 255,
-                b: nextProps.value.b * 255,
-                a: nextProps.value instanceof Color4 ? nextProps.value.a : 1,
-            };
+            nextState.color =  nextProps.value;
             nextState.hex = nextProps.value.toHexString();
         }
 
         return diffProps
-            || nextProps.disableAlpha !== this.props.disableAlpha 
             || nextState.hex !== this.state.hex
             || nextState.pickerEnabled !== this.state.pickerEnabled;
     }
@@ -88,8 +73,8 @@ export class ColorPickerLineComponent extends React.Component<IColorPickerCompon
 
         return (
             <div className="color-picker">
-                <div className="color-rect"  ref={this._floatHostRef} 
-                    style={{background: this.state.hex}} 
+                <div className="color-rect"  ref={this._floatHostRef}
+                    style={{background: this.state.hex}}
                     onClick={() => this.setState({pickerEnabled: true})}>
 
                 </div>
@@ -103,27 +88,18 @@ export class ColorPickerLineComponent extends React.Component<IColorPickerCompon
                                 this.setState({pickerEnabled: false});
                             }}>
                             <div className="color-picker-float" ref={this._floatRef}>
-                                <SketchPicker color={color} 
-                                    disableAlpha={this.props.disableAlpha}
-                                    onChange={(color) => {
-                                        let hex: string;
-
-                                        if (this.props.disableAlpha) {
-                                            let newColor3 = Color3.FromInts(color.rgb.r, color.rgb.g, color.rgb.b);
-                                            hex = newColor3.toHexString();    
-                                        } else {
-                                            let newColor4 = Color4.FromInts(color.rgb.r, color.rgb.g, color.rgb.b, 255 * (color.rgb.a || 0));
-                                            hex = newColor4.toHexString();   
-                                        }
-                                        this.setState({hex: hex, color: color.rgb});
+                                <ColorPicker color={color}
+                                    onColorChanged={(color: Color3 | Color4) => {
+                                        const hex: string = color.toHexString();
+                                        this.setState({ hex, color });
                                         this.props.onColorChanged(hex);
                                     }}
                                 />
                             </div>
                         </div>
                     </>
-                }                
+                }
             </div>
         );
     }
-}
+}

+ 1 - 1
inspector/src/components/actionTabs/tabs/gradientStepComponent.tsx

@@ -53,7 +53,7 @@ export class GradientStepComponent extends React.Component<IGradientStepComponen
                     {`#${this.props.lineIndex}`}
                 </div>
                 <div className="color">
-                    <ColorPickerLineComponent value={step.color} disableAlpha={true}
+                    <ColorPickerLineComponent value={step.color}
                             onColorChanged={color => {
                                     this.updateColor(color);
                             }} 

+ 16 - 4
inspector/src/components/actionTabs/tabs/propertyGrids/materials/textures/toolBar.tsx

@@ -1,6 +1,7 @@
 import * as React from 'react';
-import { SketchPicker } from 'react-color';
 import { IToolData, IToolType, IMetadata } from './textureEditorComponent';
+import { Color3, Color4 } from 'babylonjs/Maths/math.color';
+import { ColorPicker } from '../../../../../controls/colorPicker/colorPicker';
 
 export interface ITool extends IToolData {
     instance: IToolType;
@@ -36,9 +37,10 @@ export class ToolBar extends React.Component<IToolBarProps, IToolBarState> {
     computeRGBAColor() {
         const opacityInt = Math.floor(this.props.metadata.alpha * 255);
         const opacityHex = opacityInt.toString(16).padStart(2, '0');
-        return `${this.props.metadata.color}${opacityHex}`;
+
+        return Color4.FromHexString(`${this.props.metadata.color}${opacityHex}`);
     }
-    
+
     shouldComponentUpdate(nextProps: IToolBarProps) {
         return (nextProps.tools != this.props.tools || nextProps.activeToolIndex !== this.props.activeToolIndex || nextProps.metadata != this.props.metadata || nextProps.pickerOpen != this.props.pickerOpen);
     }
@@ -76,7 +78,17 @@ export class ToolBar extends React.Component<IToolBarProps, IToolBarState> {
             {
                 this.props.pickerOpen &&
                 <div id='color-picker' ref={this.props.pickerRef}>
-                    <SketchPicker disableAlpha={!this.props.hasAlpha} color={this.computeRGBAColor()}  onChange={color => this.props.setMetadata({color: color.hex, alpha: color.rgb.a})}/>
+                    <ColorPicker
+                        color={this.computeRGBAColor()}
+                        onColorChanged={
+                            (color: Color3 | Color4) => {
+                                const metadata = { color: color.toHexString(true), alpha: (color as unknown as Color4).a};
+                                if (metadata.color !== this.props.metadata.color ||
+                                    metadata.alpha !== this.props.metadata.alpha) {
+                                    this.props.setMetadata(metadata);
+                                }
+                            }
+                        }/>
                 </div>
             }
         </div>;

+ 1 - 1
inspector/src/components/actionTabs/tabs/propertyGrids/particleSystems/colorGradientStepGridComponent.tsx

@@ -85,7 +85,7 @@ export class ColorGradientStepGridComponent extends React.Component<IColorGradie
                 <div className="color1">
                     <ColorPickerLineComponent value={gradient instanceof Color3Gradient ? gradient.color : gradient.color1} onColorChanged={color => {
                         this.updateColor1(color);
-                    }} disableAlpha={gradient instanceof Color3Gradient}/>
+                    }}/>
                 </div>
                 {
                     this.props.host instanceof ParticleSystem && gradient instanceof ColorGradient &&

+ 53 - 0
inspector/src/components/controls/colorPicker/colorComponentEntry.tsx

@@ -0,0 +1,53 @@
+import * as React from "react";
+
+export interface IColorComponentEntryProps {
+    value: number,
+    label: string,
+    max?: number,
+    min?: number,
+    onChange: (value: number) => void
+}
+
+export class ColorComponentEntry extends React.Component<IColorComponentEntryProps> {
+    constructor(props: IColorComponentEntryProps) {
+        super(props);
+    }
+
+    updateValue(valueString: string) {
+        if (/[^0-9\.\-]/g.test(valueString)) {
+            return;
+        }
+
+        let valueAsNumber = parseInt(valueString);
+
+        if (isNaN(valueAsNumber)) {
+            return;
+        }
+        if(this.props.max != undefined && (valueAsNumber > this.props.max)) {
+            valueAsNumber = this.props.max;
+        }
+        if(this.props.min != undefined && (valueAsNumber < this.props.min)) {
+            valueAsNumber = this.props.min;
+        }
+
+        this.props.onChange(valueAsNumber);
+    }
+
+    public render() {
+        return (
+            <div className="color-picker-component">
+                <div className="color-picker-component-value">
+                    <input type="number" step={1} className="numeric-input"
+                        value={this.props.value} 
+                        onChange={(evt) => this.updateValue(evt.target.value)} />
+                </div>                        
+                <div className="color-picker-component-label">
+                    {
+                        this.props.label
+                    }
+                </div>
+            </div>
+        )
+    }
+
+}

+ 181 - 0
inspector/src/components/controls/colorPicker/colorPicker.scss

@@ -0,0 +1,181 @@
+.color-picker-container {
+    width: 320px;
+    height: 300px;
+    background-color: white;
+    display: grid;    
+    grid-template-columns: 100%;
+    grid-template-rows: 50% 50px 60px 40px 1fr;
+    font-family: "acumin-pro-condensed";
+    font-weight: normal;   
+    font-size: 14px;
+    
+    .color-picker-saturation {
+        grid-row: 1;
+        grid-column: 1;
+        display: grid;
+        grid-template-columns: 100%;
+        grid-template-rows: 100%;
+        position: relative;
+        cursor: pointer;
+    
+        .color-picker-saturation-white {
+            grid-row: 1;
+            grid-column: 1;
+
+            background: -webkit-linear-gradient(to right, #fff, rgba(255,255,255,0));
+            background: linear-gradient(to right, #fff, rgba(255,255,255,0));
+        }
+
+        .color-picker-saturation-black {
+            grid-row: 1;
+            grid-column: 1;
+
+            background: -webkit-linear-gradient(to top, #000, rgba(0,0,0,0));
+            background: linear-gradient(to top, #000, rgba(0,0,0,0));
+        }
+
+        .color-picker-saturation-cursor {
+            pointer-events: none;
+            width: 4px;
+            height: 4px;
+            box-shadow: 0 0 0 1.5px #fff, inset 0 0 1px 1px rgba(0,0,0,.3), 0 0 1px 2px rgba(0,0,0,.4);
+            border-radius: 50%;
+            transform: translate(-2px, -2px);
+            position: absolute;
+        }
+    }
+
+    .color-picker-hue {
+        grid-row: 2;
+        grid-column: 1;
+        display: grid;
+        margin: 10px;
+        grid-template-columns: 24% 76%;
+        grid-template-rows: 100%;
+
+        .color-picker-hue-color {
+            grid-row: 1;
+            grid-column: 1;
+            align-self: center;
+            justify-self: center;
+            width: 30px;
+            height: 30px;
+            border-radius: 15px;
+            border: 1px solid black;
+        }
+
+        .color-picker-hue-slider {
+            grid-row: 1;
+            grid-column: 2;
+            align-self: center;
+            height: 16px;
+            position: relative;
+            cursor: pointer;
+            
+            background: linear-gradient(to right, #f00 0%, #ff0 17%, #0f0
+                    33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%);
+            background: -webkit-linear-gradient(to right, #f00 0%, #ff0
+                17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%);                
+
+            .color-picker-hue-cursor {
+                pointer-events: none;
+                width: 8px;
+                height: 18px;
+                transform: translate(-4px, -2px);
+                background-color: rgb(248, 248, 248);
+                box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.37);
+                position: absolute;
+            }
+        }
+    }
+
+    .color-picker-component {
+        display: grid;
+        margin: 5px;
+        grid-template-columns: 100%;
+        grid-template-rows: 50% 50%;
+
+        .color-picker-component-value {
+            justify-self: center;
+            align-self: center;
+            grid-row: 1;
+            grid-column: 1;
+            margin-bottom: 4px;
+
+            input {
+                width: 50px;
+            }
+        }
+
+        .color-picker-component-label {
+            justify-self: center;
+            align-self: center;
+            grid-row: 2;
+            grid-column: 1;
+            color:black;
+        }
+    }
+
+    .color-picker-rgb {
+        grid-row: 3;
+        grid-column: 1;
+        display: grid;
+        margin: 10px;
+        grid-template-columns: 20% 6.66% 20% 6.66% 20% 6.66% 20%;
+        grid-template-rows: 100%;
+    }
+
+    .red {
+        grid-row: 1;
+        grid-column: 1;
+    }
+
+    .green {
+        grid-row: 1;
+        grid-column: 3;
+    }
+
+    .blue {
+        grid-row: 1;
+        grid-column: 5;
+    }
+
+    .alpha {
+        grid-row: 1;
+        grid-column: 7;
+
+        &.grayed {
+            opacity: 0.5;
+        }
+    }
+
+    .color-picker-hex {
+        grid-row: 4;
+        grid-column: 1;
+        display: grid;       
+        grid-template-columns: 20% 80%;
+        grid-template-rows: 100%;
+
+        .color-picker-hex-label {
+            justify-self: center;
+            align-self: center;
+            grid-row: 1;
+            grid-column: 1;
+            margin-left: 10px;
+            color:black;
+        }
+
+        .color-picker-hex-value {
+            justify-self: left;
+            align-self: center;
+            grid-row: 1;
+            grid-column: 2;
+            margin-left: 10px;
+            margin-right: 10px;
+
+            input {
+                width: 70px;
+            }
+        }
+    }
+}

+ 220 - 0
inspector/src/components/controls/colorPicker/colorPicker.tsx

@@ -0,0 +1,220 @@
+import * as React from "react";
+import { Color3, Color4 } from "babylonjs/Maths/math.color";
+import { ColorComponentEntry } from './colorComponentEntry';
+import { HexColor } from './hexColor';
+
+require("./colorPicker.scss");
+
+/**
+ * Interface used to specify creation options for color picker
+ */
+export interface IColorPickerProps {
+    color: Color3 | Color4,
+    debugMode?: boolean,
+    onColorChanged?: (color: Color3 | Color4) => void
+}
+
+/**
+ * Interface used to specify creation options for color picker
+ */
+export interface IColorPickerState {
+    color: Color3;
+    alpha: number;
+}
+
+/**
+ * Class used to create a color picker
+ */
+export class ColorPicker extends React.Component<IColorPickerProps, IColorPickerState> {
+    private _saturationRef: React.RefObject<HTMLDivElement>;
+    private _hueRef: React.RefObject<HTMLDivElement>;
+    private _isSaturationPointerDown: boolean;
+    private _isHuePointerDown: boolean;
+
+    constructor(props: IColorPickerProps) {
+        super(props);
+        if (this.props.color instanceof Color4) {
+            this.state = {color: new Color3(this.props.color.r, this.props.color.g, this.props.color.b), alpha: this.props.color.a};
+        } else {
+            this.state = {color : this.props.color.clone(), alpha: 1};
+        }
+        this._saturationRef = React.createRef();
+        this._hueRef = React.createRef();
+    }
+
+    onSaturationPointerDown(evt: React.PointerEvent<HTMLDivElement>) {
+        this._evaluateSaturation(evt);
+        this._isSaturationPointerDown = true;
+
+        evt.currentTarget.setPointerCapture(evt.pointerId);
+    }
+    
+    onSaturationPointerUp(evt: React.PointerEvent<HTMLDivElement>) {
+        this._isSaturationPointerDown = false;
+        evt.currentTarget.releasePointerCapture(evt.pointerId);
+    }
+
+    onSaturationPointerMove(evt: React.PointerEvent<HTMLDivElement>) {
+        if (!this._isSaturationPointerDown) {
+            return;
+        }
+        this._evaluateSaturation(evt);
+    }
+
+    onHuePointerDown(evt: React.PointerEvent<HTMLDivElement>) {
+        this._evaluateHue(evt);
+        this._isHuePointerDown = true;
+
+        evt.currentTarget.setPointerCapture(evt.pointerId);
+    }
+    
+    onHuePointerUp(evt: React.PointerEvent<HTMLDivElement>) {
+        this._isHuePointerDown = false;
+        evt.currentTarget.releasePointerCapture(evt.pointerId);
+    }
+
+    onHuePointerMove(evt: React.PointerEvent<HTMLDivElement>) {
+        if (!this._isHuePointerDown) {
+            return;
+        }
+        this._evaluateHue(evt);
+    }
+
+    private _evaluateSaturation(evt: React.PointerEvent<HTMLDivElement>) {
+        let left = evt.nativeEvent.offsetX;
+        let top = evt.nativeEvent.offsetY;
+      
+        const saturation =  Math.min(1, Math.max(0.0001, left / this._saturationRef.current!.clientWidth));
+        const value = Math.min(1, Math.max(0.0001, 1 - (top / this._saturationRef.current!.clientHeight)));
+
+        if (this.props.debugMode) {
+            console.log("Saturation: " + saturation);
+            console.log("Value: " + value);
+        }
+
+        let hsv = this.state.color.toHSV();
+        Color3.HSVtoRGBToRef(hsv.r, saturation, value, this.state.color);
+        this.setState({color: this.state.color});
+    }
+
+    private _evaluateHue(evt: React.PointerEvent<HTMLDivElement>) {
+        let left = evt.nativeEvent.offsetX;
+      
+        const hue = 360 * Math.min(0.9999, Math.max(0.0001, left / this._hueRef.current!.clientWidth));
+
+        if (this.props.debugMode) {
+            console.log("Hue: " + hue);
+        }
+
+        let hsv = this.state.color.toHSV();
+        Color3.HSVtoRGBToRef(hue, Math.max(hsv.g, 0.0001), hsv.b, this.state.color);
+        this.setState({color: this.state.color});
+    }
+
+    componentDidUpdate() {
+        this.raiseOnColorChanged();
+    }
+
+    raiseOnColorChanged() {
+        if (!this.props.onColorChanged) {
+            return;
+        }
+
+        if (this.props.color instanceof Color4) {
+            let newColor4 = Color4.FromColor3(this.state.color, this.state.alpha);
+
+            this.props.onColorChanged(newColor4);
+
+            return;
+        }
+
+        this.props.onColorChanged(this.state.color.clone());
+    } 
+
+    public render() {
+        let colorHex = this.state.color.toHexString();
+        let hsv = this.state.color.toHSV();
+        let colorRef = new Color3();
+        Color3.HSVtoRGBToRef(hsv.r, 1, 1, colorRef)
+        let colorHexRef = colorRef.toHexString();
+        let hasAlpha = this.props.color instanceof Color4;
+
+        return (
+            <div className="color-picker-container">
+                <div className="color-picker-saturation"  
+                    onPointerMove={e => this.onSaturationPointerMove(e)}               
+                    onPointerDown={e => this.onSaturationPointerDown(e)}
+                    onPointerUp={e => this.onSaturationPointerUp(e)}
+                    ref={this._saturationRef}
+                    style={{
+                        background: colorHexRef
+                    }}>
+                    <div className="color-picker-saturation-white">
+                    </div>
+                    <div className="color-picker-saturation-black">
+                    </div>
+                    <div className="color-picker-saturation-cursor" style={{
+                        top: `${ -(hsv.b * 100) + 100 }%`,
+                        left: `${ hsv.g * 100 }%`,
+                    }}>
+                    </div>
+                </div>
+                <div className="color-picker-hue">
+                    <div className="color-picker-hue-color" style={{
+                        background: colorHex
+                    }}>
+                    </div>
+                    <div className="color-picker-hue-slider"                    
+                        ref={this._hueRef}
+                        onPointerMove={e => this.onHuePointerMove(e)}               
+                        onPointerDown={e => this.onHuePointerDown(e)}
+                        onPointerUp={e => this.onHuePointerUp(e)}
+                    >                    
+                        <div className="color-picker-hue-cursor" style={{
+                            left: `${ (hsv.r / 360.0) * 100 }%`,
+                            border: `1px solid ` + colorHexRef
+                        }}>                    
+                        </div>
+                    </div>
+                </div>
+                <div className="color-picker-rgb">
+                    <div className="red">
+                        <ColorComponentEntry label="R" min={0} max={255} value={this.state.color.r * 255 | 0} onChange={value => {
+                            this.state.color.r = value / 255.0;
+                            this.forceUpdate();
+                        }}/>
+                    </div>   
+                    <div className="green">
+                        <ColorComponentEntry label="G" min={0} max={255}  value={this.state.color.g * 255 | 0} onChange={value => {
+                            this.state.color.g = value / 255.0;
+                            this.forceUpdate();
+                        }}/>
+                    </div>  
+                    <div className="blue">
+                        <ColorComponentEntry label="B" min={0} max={255}  value={this.state.color.b * 255 | 0} onChange={value => {
+                            this.state.color.b = value / 255.0;
+                            this.forceUpdate();
+                        }}/>
+                    </div>        
+                    <div className={"alpha" + (hasAlpha ? "" : " grayed")}>
+                        <ColorComponentEntry label="A" min={0} max={255} value={this.state.alpha * 255 | 0} onChange={value => {
+                                this.setState({alpha: value / 255.0});
+                                this.forceUpdate();
+                        }}/>
+                    </div>   
+                </div>  
+                <div className="color-picker-hex">
+                    <div className="color-picker-hex-label">
+                        Hex
+                    </div>
+                    <div className="color-picker-hex-value">     
+                        <HexColor expectedLength={6} value={colorHex} onChange={value => {
+                            this.setState({color: Color3.FromHexString(value)});
+                        }}/>            
+                    </div>
+                </div>
+            </div>
+        );
+    }
+}
+

+ 45 - 0
inspector/src/components/controls/colorPicker/hexColor.tsx

@@ -0,0 +1,45 @@
+import * as React from "react";
+
+export interface IHexColorProps {
+    value: string,
+    expectedLength: number,
+    onChange: (value: string) => void
+}
+
+export class HexColor extends React.Component<IHexColorProps, {hex: string}> {
+    constructor(props: IHexColorProps) {
+        super(props);
+
+        this.state = {hex: this.props.value.replace("#", "")}
+    }
+
+    shouldComponentUpdate(nextProps: IHexColorProps, nextState: {hex: string}) {
+        if (nextProps.value!== this.props.value) {
+            nextState.hex = nextProps.value.replace("#", "");
+        }
+
+        return true;
+    }
+
+    updateHexValue(valueString: string) {
+        if (valueString != "" && /^[0-9A-Fa-f]+$/g.test(valueString) == false) {
+            return;
+        }
+    
+        this.setState({hex: valueString});
+
+        if(valueString.length !== this.props.expectedLength) {
+            return;
+        }
+       
+        this.props.onChange("#" + valueString);
+    }
+
+    public render() {
+        return (
+            <input type="string" className="hex-input" value={this.state.hex} 
+                onChange={evt => this.updateHexValue(evt.target.value)}/>   
+        )
+    }
+
+}

+ 1 - 1
nodeEditor/src/components/propertyTab/propertyTab.scss

@@ -162,7 +162,7 @@
     
     .textInputArea {
         padding-left: $line-padding-left;
-        height: 100%;
+        height: 50px;
         display: grid;
         grid-template-columns: 1fr 120px;
 

+ 1 - 1
nodeEditor/src/diagram/properties/gradientStepComponent.tsx

@@ -54,7 +54,7 @@ export class GradientStepComponent extends React.Component<IGradientStepComponen
                     {`#${this.props.lineIndex}`}
                 </div>
                 <div className="color">
-                    <ColorPickerLineComponent value={step.color} disableAlpha={true} globalState={this.props.globalState} 
+                    <ColorPickerLineComponent value={step.color} globalState={this.props.globalState} 
                             onColorChanged={color => {
                                     this.updateColor(color);
                             }} 

+ 1 - 1
nodeEditor/src/sharedComponents/color3LineComponent.tsx

@@ -148,7 +148,7 @@ export class Color3LineComponent extends React.Component<IColor3LineComponentPro
                         {this.props.label}
                     </div>
                     <div className="color3">
-                        <ColorPickerLineComponent value={this.state.color} disableAlpha={true} globalState={this.props.globalState} onColorChanged={color => {
+                        <ColorPickerLineComponent value={this.state.color} globalState={this.props.globalState} onColorChanged={color => {
                                 this.onChange(color);
                             }} />  
                     </div>

+ 53 - 0
nodeEditor/src/sharedComponents/colorPicker/colorComponentEntry.tsx

@@ -0,0 +1,53 @@
+import * as React from "react";
+
+export interface IColorComponentEntryProps {
+    value: number,
+    label: string,
+    max?: number,
+    min?: number,
+    onChange: (value: number) => void
+}
+
+export class ColorComponentEntry extends React.Component<IColorComponentEntryProps> {
+    constructor(props: IColorComponentEntryProps) {
+        super(props);
+    }
+
+    updateValue(valueString: string) {
+        if (/[^0-9\.\-]/g.test(valueString)) {
+            return;
+        }
+
+        let valueAsNumber = parseInt(valueString);
+
+        if (isNaN(valueAsNumber)) {
+            return;
+        }
+        if(this.props.max != undefined && (valueAsNumber > this.props.max)) {
+            valueAsNumber = this.props.max;
+        }
+        if(this.props.min != undefined && (valueAsNumber < this.props.min)) {
+            valueAsNumber = this.props.min;
+        }
+
+        this.props.onChange(valueAsNumber);
+    }
+
+    public render() {
+        return (
+            <div className="color-picker-component">
+                <div className="color-picker-component-value">
+                    <input type="number" step={1} className="numeric-input"
+                        value={this.props.value} 
+                        onChange={(evt) => this.updateValue(evt.target.value)} />
+                </div>                        
+                <div className="color-picker-component-label">
+                    {
+                        this.props.label
+                    }
+                </div>
+            </div>
+        )
+    }
+
+}

+ 181 - 0
nodeEditor/src/sharedComponents/colorPicker/colorPicker.scss

@@ -0,0 +1,181 @@
+.color-picker-container {
+    width: 320px;
+    height: 300px;
+    background-color: white;
+    display: grid;    
+    grid-template-columns: 100%;
+    grid-template-rows: 50% 50px 60px 40px 1fr;
+    font-family: "acumin-pro-condensed";
+    font-weight: normal;   
+    font-size: 14px;
+    
+    .color-picker-saturation {
+        grid-row: 1;
+        grid-column: 1;
+        display: grid;
+        grid-template-columns: 100%;
+        grid-template-rows: 100%;
+        position: relative;
+        cursor: pointer;
+    
+        .color-picker-saturation-white {
+            grid-row: 1;
+            grid-column: 1;
+
+            background: -webkit-linear-gradient(to right, #fff, rgba(255,255,255,0));
+            background: linear-gradient(to right, #fff, rgba(255,255,255,0));
+        }
+
+        .color-picker-saturation-black {
+            grid-row: 1;
+            grid-column: 1;
+
+            background: -webkit-linear-gradient(to top, #000, rgba(0,0,0,0));
+            background: linear-gradient(to top, #000, rgba(0,0,0,0));
+        }
+
+        .color-picker-saturation-cursor {
+            pointer-events: none;
+            width: 4px;
+            height: 4px;
+            box-shadow: 0 0 0 1.5px #fff, inset 0 0 1px 1px rgba(0,0,0,.3), 0 0 1px 2px rgba(0,0,0,.4);
+            border-radius: 50%;
+            transform: translate(-2px, -2px);
+            position: absolute;
+        }
+    }
+
+    .color-picker-hue {
+        grid-row: 2;
+        grid-column: 1;
+        display: grid;
+        margin: 10px;
+        grid-template-columns: 24% 76%;
+        grid-template-rows: 100%;
+
+        .color-picker-hue-color {
+            grid-row: 1;
+            grid-column: 1;
+            align-self: center;
+            justify-self: center;
+            width: 30px;
+            height: 30px;
+            border-radius: 15px;
+            border: 1px solid black;
+        }
+
+        .color-picker-hue-slider {
+            grid-row: 1;
+            grid-column: 2;
+            align-self: center;
+            height: 16px;
+            position: relative;
+            cursor: pointer;
+            
+            background: linear-gradient(to right, #f00 0%, #ff0 17%, #0f0
+                    33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%);
+            background: -webkit-linear-gradient(to right, #f00 0%, #ff0
+                17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%);                
+
+            .color-picker-hue-cursor {
+                pointer-events: none;
+                width: 8px;
+                height: 18px;
+                transform: translate(-4px, -2px);
+                background-color: rgb(248, 248, 248);
+                box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.37);
+                position: absolute;
+            }
+        }
+    }
+
+    .color-picker-component {
+        display: grid;
+        margin: 5px;
+        grid-template-columns: 100%;
+        grid-template-rows: 50% 50%;
+
+        .color-picker-component-value {
+            justify-self: center;
+            align-self: center;
+            grid-row: 1;
+            grid-column: 1;
+            margin-bottom: 4px;
+
+            input {
+                width: 50px;
+            }
+        }
+
+        .color-picker-component-label {
+            justify-self: center;
+            align-self: center;
+            grid-row: 2;
+            grid-column: 1;
+            color:black;
+        }
+    }
+
+    .color-picker-rgb {
+        grid-row: 3;
+        grid-column: 1;
+        display: grid;
+        margin: 10px;
+        grid-template-columns: 20% 6.66% 20% 6.66% 20% 6.66% 20%;
+        grid-template-rows: 100%;
+    }
+
+    .red {
+        grid-row: 1;
+        grid-column: 1;
+    }
+
+    .green {
+        grid-row: 1;
+        grid-column: 3;
+    }
+
+    .blue {
+        grid-row: 1;
+        grid-column: 5;
+    }
+
+    .alpha {
+        grid-row: 1;
+        grid-column: 7;
+
+        &.grayed {
+            opacity: 0.5;
+        }
+    }
+
+    .color-picker-hex {
+        grid-row: 4;
+        grid-column: 1;
+        display: grid;       
+        grid-template-columns: 20% 80%;
+        grid-template-rows: 100%;
+
+        .color-picker-hex-label {
+            justify-self: center;
+            align-self: center;
+            grid-row: 1;
+            grid-column: 1;
+            margin-left: 10px;
+            color:black;
+        }
+
+        .color-picker-hex-value {
+            justify-self: left;
+            align-self: center;
+            grid-row: 1;
+            grid-column: 2;
+            margin-left: 10px;
+            margin-right: 10px;
+
+            input {
+                width: 70px;
+            }
+        }
+    }
+}

+ 220 - 0
nodeEditor/src/sharedComponents/colorPicker/colorPicker.tsx

@@ -0,0 +1,220 @@
+import * as React from "react";
+import { Color3, Color4 } from "babylonjs/Maths/math.color";
+import { ColorComponentEntry } from './colorComponentEntry';
+import { HexColor } from './hexColor';
+
+require("./colorPicker.scss");
+
+/**
+ * Interface used to specify creation options for color picker
+ */
+export interface IColorPickerProps {
+    color: Color3 | Color4,
+    debugMode?: boolean,
+    onColorChanged?: (color: Color3 | Color4) => void
+}
+
+/**
+ * Interface used to specify creation options for color picker
+ */
+export interface IColorPickerState {
+    color: Color3;
+    alpha: number;
+}
+
+/**
+ * Class used to create a color picker
+ */
+export class ColorPicker extends React.Component<IColorPickerProps, IColorPickerState> {
+    private _saturationRef: React.RefObject<HTMLDivElement>;
+    private _hueRef: React.RefObject<HTMLDivElement>;
+    private _isSaturationPointerDown: boolean;
+    private _isHuePointerDown: boolean;
+
+    constructor(props: IColorPickerProps) {
+        super(props);
+        if (this.props.color instanceof Color4) {
+            this.state = {color: new Color3(this.props.color.r, this.props.color.g, this.props.color.b), alpha: this.props.color.a};
+        } else {
+            this.state = {color : this.props.color.clone(), alpha: 1};
+        }
+        this._saturationRef = React.createRef();
+        this._hueRef = React.createRef();
+    }
+
+    onSaturationPointerDown(evt: React.PointerEvent<HTMLDivElement>) {
+        this._evaluateSaturation(evt);
+        this._isSaturationPointerDown = true;
+
+        evt.currentTarget.setPointerCapture(evt.pointerId);
+    }
+    
+    onSaturationPointerUp(evt: React.PointerEvent<HTMLDivElement>) {
+        this._isSaturationPointerDown = false;
+        evt.currentTarget.releasePointerCapture(evt.pointerId);
+    }
+
+    onSaturationPointerMove(evt: React.PointerEvent<HTMLDivElement>) {
+        if (!this._isSaturationPointerDown) {
+            return;
+        }
+        this._evaluateSaturation(evt);
+    }
+
+    onHuePointerDown(evt: React.PointerEvent<HTMLDivElement>) {
+        this._evaluateHue(evt);
+        this._isHuePointerDown = true;
+
+        evt.currentTarget.setPointerCapture(evt.pointerId);
+    }
+    
+    onHuePointerUp(evt: React.PointerEvent<HTMLDivElement>) {
+        this._isHuePointerDown = false;
+        evt.currentTarget.releasePointerCapture(evt.pointerId);
+    }
+
+    onHuePointerMove(evt: React.PointerEvent<HTMLDivElement>) {
+        if (!this._isHuePointerDown) {
+            return;
+        }
+        this._evaluateHue(evt);
+    }
+
+    private _evaluateSaturation(evt: React.PointerEvent<HTMLDivElement>) {
+        let left = evt.nativeEvent.offsetX;
+        let top = evt.nativeEvent.offsetY;
+      
+        const saturation =  Math.min(1, Math.max(0.0001, left / this._saturationRef.current!.clientWidth));
+        const value = Math.min(1, Math.max(0.0001, 1 - (top / this._saturationRef.current!.clientHeight)));
+
+        if (this.props.debugMode) {
+            console.log("Saturation: " + saturation);
+            console.log("Value: " + value);
+        }
+
+        let hsv = this.state.color.toHSV();
+        Color3.HSVtoRGBToRef(hsv.r, saturation, value, this.state.color);
+        this.setState({color: this.state.color});
+    }
+
+    private _evaluateHue(evt: React.PointerEvent<HTMLDivElement>) {
+        let left = evt.nativeEvent.offsetX;
+      
+        const hue = 360 * Math.min(0.9999, Math.max(0.0001, left / this._hueRef.current!.clientWidth));
+
+        if (this.props.debugMode) {
+            console.log("Hue: " + hue);
+        }
+
+        let hsv = this.state.color.toHSV();
+        Color3.HSVtoRGBToRef(hue, Math.max(hsv.g, 0.0001), hsv.b, this.state.color);
+        this.setState({color: this.state.color});
+    }
+
+    componentDidUpdate() {
+        this.raiseOnColorChanged();
+    }
+
+    raiseOnColorChanged() {
+        if (!this.props.onColorChanged) {
+            return;
+        }
+
+        if (this.props.color instanceof Color4) {
+            let newColor4 = Color4.FromColor3(this.state.color, this.state.alpha);
+
+            this.props.onColorChanged(newColor4);
+
+            return;
+        }
+
+        this.props.onColorChanged(this.state.color.clone());
+    } 
+
+    public render() {
+        let colorHex = this.state.color.toHexString();
+        let hsv = this.state.color.toHSV();
+        let colorRef = new Color3();
+        Color3.HSVtoRGBToRef(hsv.r, 1, 1, colorRef)
+        let colorHexRef = colorRef.toHexString();
+        let hasAlpha = this.props.color instanceof Color4;
+
+        return (
+            <div className="color-picker-container">
+                <div className="color-picker-saturation"  
+                    onPointerMove={e => this.onSaturationPointerMove(e)}               
+                    onPointerDown={e => this.onSaturationPointerDown(e)}
+                    onPointerUp={e => this.onSaturationPointerUp(e)}
+                    ref={this._saturationRef}
+                    style={{
+                        background: colorHexRef
+                    }}>
+                    <div className="color-picker-saturation-white">
+                    </div>
+                    <div className="color-picker-saturation-black">
+                    </div>
+                    <div className="color-picker-saturation-cursor" style={{
+                        top: `${ -(hsv.b * 100) + 100 }%`,
+                        left: `${ hsv.g * 100 }%`,
+                    }}>
+                    </div>
+                </div>
+                <div className="color-picker-hue">
+                    <div className="color-picker-hue-color" style={{
+                        background: colorHex
+                    }}>
+                    </div>
+                    <div className="color-picker-hue-slider"                    
+                        ref={this._hueRef}
+                        onPointerMove={e => this.onHuePointerMove(e)}               
+                        onPointerDown={e => this.onHuePointerDown(e)}
+                        onPointerUp={e => this.onHuePointerUp(e)}
+                    >                    
+                        <div className="color-picker-hue-cursor" style={{
+                            left: `${ (hsv.r / 360.0) * 100 }%`,
+                            border: `1px solid ` + colorHexRef
+                        }}>                    
+                        </div>
+                    </div>
+                </div>
+                <div className="color-picker-rgb">
+                    <div className="red">
+                        <ColorComponentEntry label="R" min={0} max={255} value={this.state.color.r * 255 | 0} onChange={value => {
+                            this.state.color.r = value / 255.0;
+                            this.forceUpdate();
+                        }}/>
+                    </div>   
+                    <div className="green">
+                        <ColorComponentEntry label="G" min={0} max={255}  value={this.state.color.g * 255 | 0} onChange={value => {
+                            this.state.color.g = value / 255.0;
+                            this.forceUpdate();
+                        }}/>
+                    </div>  
+                    <div className="blue">
+                        <ColorComponentEntry label="B" min={0} max={255}  value={this.state.color.b * 255 | 0} onChange={value => {
+                            this.state.color.b = value / 255.0;
+                            this.forceUpdate();
+                        }}/>
+                    </div>        
+                    <div className={"alpha" + (hasAlpha ? "" : " grayed")}>
+                        <ColorComponentEntry label="A" min={0} max={255} value={this.state.alpha * 255 | 0} onChange={value => {
+                                this.setState({alpha: value / 255.0});
+                                this.forceUpdate();
+                        }}/>
+                    </div>   
+                </div>  
+                <div className="color-picker-hex">
+                    <div className="color-picker-hex-label">
+                        Hex
+                    </div>
+                    <div className="color-picker-hex-value">     
+                        <HexColor expectedLength={6} value={colorHex} onChange={value => {
+                            this.setState({color: Color3.FromHexString(value)});
+                        }}/>            
+                    </div>
+                </div>
+            </div>
+        );
+    }
+}
+

+ 45 - 0
nodeEditor/src/sharedComponents/colorPicker/hexColor.tsx

@@ -0,0 +1,45 @@
+import * as React from "react";
+
+export interface IHexColorProps {
+    value: string,
+    expectedLength: number,
+    onChange: (value: string) => void
+}
+
+export class HexColor extends React.Component<IHexColorProps, {hex: string}> {
+    constructor(props: IHexColorProps) {
+        super(props);
+
+        this.state = {hex: this.props.value.replace("#", "")}
+    }
+
+    shouldComponentUpdate(nextProps: IHexColorProps, nextState: {hex: string}) {
+        if (nextProps.value!== this.props.value) {
+            nextState.hex = nextProps.value.replace("#", "");
+        }
+
+        return true;
+    }
+
+    updateHexValue(valueString: string) {
+        if (valueString != "" && /^[0-9A-Fa-f]+$/g.test(valueString) == false) {
+            return;
+        }
+    
+        this.setState({hex: valueString});
+
+        if(valueString.length !== this.props.expectedLength) {
+            return;
+        }
+       
+        this.props.onChange("#" + valueString);
+    }
+
+    public render() {
+        return (
+            <input type="string" className="hex-input" value={this.state.hex} 
+                onChange={evt => this.updateHexValue(evt.target.value)}/>   
+        )
+    }
+
+}

+ 20 - 46
nodeEditor/src/sharedComponents/colorPickerComponent.tsx

@@ -1,40 +1,29 @@
 import * as React from "react";
 import { Color4, Color3 } from 'babylonjs/Maths/math.color';
-import { SketchPicker } from 'react-color';
 import { GlobalState } from '../globalState';
+import { ColorPicker } from './colorPicker/colorPicker';
 
 export interface IColorPickerComponentProps {
     value: Color4 | Color3;
     onColorChanged: (newOne: string) => void;
     globalState: GlobalState;
-    disableAlpha?: boolean;
 }
 
 interface IColorPickerComponentState {
     pickerEnabled: boolean;
-    color: {
-        r: number,
-        g: number,
-        b: number,
-        a?: number
-    },
-    hex: string
+    color: Color3 | Color4;
+    hex: string;
 }
 
 export class ColorPickerLineComponent extends React.Component<IColorPickerComponentProps, IColorPickerComponentState> {
-    private _floatRef: React.RefObject<HTMLDivElement>
-    private _floatHostRef: React.RefObject<HTMLDivElement>
+    private _floatRef: React.RefObject<HTMLDivElement>;
+    private _floatHostRef: React.RefObject<HTMLDivElement>;
 
     constructor(props: IColorPickerComponentProps) {
         super(props);
 
-        this.state = {pickerEnabled: false, color: {
-            r: this.props.value.r * 255,
-            g: this.props.value.g * 255,
-            b: this.props.value.b * 255,
-            a: this.props.value instanceof Color4 ? this.props.value.a * 100 : 100,
-        }, hex: this.props.value.toHexString()};
-        
+        this.state = {pickerEnabled: false, color: this.props.value, hex: this.props.value.toHexString()};
+
         this._floatRef = React.createRef();
         this._floatHostRef = React.createRef();
     }
@@ -59,21 +48,15 @@ export class ColorPickerLineComponent extends React.Component<IColorPickerCompon
     }
 
     shouldComponentUpdate(nextProps: IColorPickerComponentProps, nextState: IColorPickerComponentState) {
-        let result = nextProps.value.toHexString() !== this.props.value.toHexString() 
-            || nextProps.disableAlpha !== this.props.disableAlpha 
+        let result = nextProps.value.toHexString() !== this.props.value.toHexString()            
             || nextState.hex !== this.state.hex
             || nextState.pickerEnabled !== this.state.pickerEnabled;
-        
-        if(result) {
-            nextState.color =  {
-                r: nextProps.value.r * 255,
-                g: nextProps.value.g * 255,
-                b: nextProps.value.b * 255,
-                a: nextProps.value instanceof Color4 ? nextProps.value.a : 1,
-            };
+
+        if (result) {
+            nextState.color = nextProps.value;
             nextState.hex = nextProps.value.toHexString();
         }
-        return result;   
+        return result;
     }
 
     componentDidUpdate() {
@@ -96,8 +79,8 @@ export class ColorPickerLineComponent extends React.Component<IColorPickerCompon
 
         return (
             <div className="color-picker">
-                <div className="color-rect"  ref={this._floatHostRef} 
-                    style={{background: this.state.hex}} 
+                <div className="color-rect"  ref={this._floatHostRef}
+                    style={{background: this.state.hex}}
                     onClick={() => this.setPickerState(true)}>
 
                 </div>
@@ -106,26 +89,17 @@ export class ColorPickerLineComponent extends React.Component<IColorPickerCompon
                     <>
                         <div className="color-picker-cover" onClick={() => this.setPickerState(false)}></div>
                         <div className="color-picker-float" ref={this._floatRef}>
-                            <SketchPicker color={color} 
-                                disableAlpha={this.props.disableAlpha}
-                                onChange={(color) => {
-                                    let hex: string;
-
-                                    if (this.props.disableAlpha) {
-                                        let newColor3 = Color3.FromInts(color.rgb.r, color.rgb.g, color.rgb.b);
-                                        hex = newColor3.toHexString();    
-                                    } else {
-                                        let newColor4 = Color4.FromInts(color.rgb.r, color.rgb.g, color.rgb.b, 255 * (color.rgb.a || 0));
-                                        hex = newColor4.toHexString();   
-                                    }
-                                    this.setState({hex: hex, color: color.rgb});
+                            <ColorPicker color={color}
+                                onColorChanged={(color: Color3 | Color4) => {
+                                    const hex = color.toHexString();
+                                    this.setState({ hex, color });
                                     this.props.onColorChanged(hex);
                                 }}
                             />
                         </div>
                     </>
-                }                
+                }
             </div>
         );
     }
-}
+}

+ 0 - 1
package.json

@@ -90,7 +90,6 @@
         "prompt": "^1.0.0",
         "re-resizable": "~4.9.1",
         "react": "~16.13.1",
-        "react-color": "github:RaananW/react-color#master",
         "react-contextmenu": "~2.14.0",
         "react-dom": "~16.13.1",
         "sass-loader": "^8.0.2",