colorPicker.tsx 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. import * as React from "react";
  2. import { Color3, Color4 } from "babylonjs/Maths/math.color";
  3. import { ColorComponentEntry } from './colorComponentEntry';
  4. import { HexColor } from './hexColor';
  5. require("./colorPicker.scss");
  6. /**
  7. * Interface used to specify creation options for color picker
  8. */
  9. export interface IColorPickerProps {
  10. color: Color3 | Color4,
  11. debugMode?: boolean,
  12. onColorChanged?: (color: Color3 | Color4) => void
  13. }
  14. /**
  15. * Interface used to specify creation options for color picker
  16. */
  17. export interface IColorPickerState {
  18. color: Color3;
  19. alpha: number;
  20. }
  21. /**
  22. * Class used to create a color picker
  23. */
  24. export class ColorPicker extends React.Component<IColorPickerProps, IColorPickerState> {
  25. private _saturationRef: React.RefObject<HTMLDivElement>;
  26. private _hueRef: React.RefObject<HTMLDivElement>;
  27. private _isSaturationPointerDown: boolean;
  28. private _isHuePointerDown: boolean;
  29. constructor(props: IColorPickerProps) {
  30. super(props);
  31. if (this.props.color instanceof Color4) {
  32. this.state = {color: new Color3(this.props.color.r, this.props.color.g, this.props.color.b), alpha: this.props.color.a};
  33. } else {
  34. this.state = {color : this.props.color.clone(), alpha: 1};
  35. }
  36. this._saturationRef = React.createRef();
  37. this._hueRef = React.createRef();
  38. }
  39. onSaturationPointerDown(evt: React.PointerEvent<HTMLDivElement>) {
  40. this._evaluateSaturation(evt);
  41. this._isSaturationPointerDown = true;
  42. evt.currentTarget.setPointerCapture(evt.pointerId);
  43. }
  44. onSaturationPointerUp(evt: React.PointerEvent<HTMLDivElement>) {
  45. this._isSaturationPointerDown = false;
  46. evt.currentTarget.releasePointerCapture(evt.pointerId);
  47. }
  48. onSaturationPointerMove(evt: React.PointerEvent<HTMLDivElement>) {
  49. if (!this._isSaturationPointerDown) {
  50. return;
  51. }
  52. this._evaluateSaturation(evt);
  53. }
  54. onHuePointerDown(evt: React.PointerEvent<HTMLDivElement>) {
  55. this._evaluateHue(evt);
  56. this._isHuePointerDown = true;
  57. evt.currentTarget.setPointerCapture(evt.pointerId);
  58. }
  59. onHuePointerUp(evt: React.PointerEvent<HTMLDivElement>) {
  60. this._isHuePointerDown = false;
  61. evt.currentTarget.releasePointerCapture(evt.pointerId);
  62. }
  63. onHuePointerMove(evt: React.PointerEvent<HTMLDivElement>) {
  64. if (!this._isHuePointerDown) {
  65. return;
  66. }
  67. this._evaluateHue(evt);
  68. }
  69. private _evaluateSaturation(evt: React.PointerEvent<HTMLDivElement>) {
  70. let left = evt.nativeEvent.offsetX;
  71. let top = evt.nativeEvent.offsetY;
  72. const saturation = Math.min(1, Math.max(0.0001, left / this._saturationRef.current!.clientWidth));
  73. const value = Math.min(1, Math.max(0.0001, 1 - (top / this._saturationRef.current!.clientHeight)));
  74. if (this.props.debugMode) {
  75. console.log("Saturation: " + saturation);
  76. console.log("Value: " + value);
  77. }
  78. let hsv = this.state.color.toHSV();
  79. Color3.HSVtoRGBToRef(hsv.r, saturation, value, this.state.color);
  80. this.setState({color: this.state.color});
  81. }
  82. private _evaluateHue(evt: React.PointerEvent<HTMLDivElement>) {
  83. let left = evt.nativeEvent.offsetX;
  84. const hue = 360 * Math.min(0.9999, Math.max(0.0001, left / this._hueRef.current!.clientWidth));
  85. if (this.props.debugMode) {
  86. console.log("Hue: " + hue);
  87. }
  88. let hsv = this.state.color.toHSV();
  89. Color3.HSVtoRGBToRef(hue, Math.max(hsv.g, 0.0001), Math.max(hsv.b, 0.0001), this.state.color);
  90. this.setState({color: this.state.color});
  91. }
  92. componentDidUpdate() {
  93. this.raiseOnColorChanged();
  94. }
  95. raiseOnColorChanged() {
  96. if (!this.props.onColorChanged) {
  97. return;
  98. }
  99. if (this.props.color instanceof Color4) {
  100. let newColor4 = Color4.FromColor3(this.state.color, this.state.alpha);
  101. this.props.onColorChanged(newColor4);
  102. return;
  103. }
  104. this.props.onColorChanged(this.state.color.clone());
  105. }
  106. public render() {
  107. let colorHex = this.state.color.toHexString();
  108. let hsv = this.state.color.toHSV();
  109. let colorRef = new Color3();
  110. Color3.HSVtoRGBToRef(hsv.r, 1, 1, colorRef)
  111. let colorHexRef = colorRef.toHexString();
  112. let hasAlpha = this.props.color instanceof Color4;
  113. return (
  114. <div className="color-picker-container">
  115. <div className="color-picker-saturation"
  116. onPointerMove={e => this.onSaturationPointerMove(e)}
  117. onPointerDown={e => this.onSaturationPointerDown(e)}
  118. onPointerUp={e => this.onSaturationPointerUp(e)}
  119. ref={this._saturationRef}
  120. style={{
  121. background: colorHexRef
  122. }}>
  123. <div className="color-picker-saturation-white">
  124. </div>
  125. <div className="color-picker-saturation-black">
  126. </div>
  127. <div className="color-picker-saturation-cursor" style={{
  128. top: `${ -(hsv.b * 100) + 100 }%`,
  129. left: `${ hsv.g * 100 }%`,
  130. }}>
  131. </div>
  132. </div>
  133. <div className="color-picker-hue">
  134. <div className="color-picker-hue-color" style={{
  135. background: colorHex
  136. }}>
  137. </div>
  138. <div className="color-picker-hue-slider"
  139. ref={this._hueRef}
  140. onPointerMove={e => this.onHuePointerMove(e)}
  141. onPointerDown={e => this.onHuePointerDown(e)}
  142. onPointerUp={e => this.onHuePointerUp(e)}
  143. >
  144. <div className="color-picker-hue-cursor" style={{
  145. left: `${ (hsv.r / 360.0) * 100 }%`,
  146. border: `1px solid ` + colorHexRef
  147. }}>
  148. </div>
  149. </div>
  150. </div>
  151. <div className="color-picker-rgb">
  152. <div className="red">
  153. <ColorComponentEntry label="R" min={0} max={255} value={Math.round(this.state.color.r * 255)} onChange={value => {
  154. this.state.color.r = value / 255.0;
  155. this.forceUpdate();
  156. }}/>
  157. </div>
  158. <div className="green">
  159. <ColorComponentEntry label="G" min={0} max={255} value={Math.round(this.state.color.g * 255)} onChange={value => {
  160. this.state.color.g = value / 255.0;
  161. this.forceUpdate();
  162. }}/>
  163. </div>
  164. <div className="blue">
  165. <ColorComponentEntry label="B" min={0} max={255} value={Math.round(this.state.color.b * 255)} onChange={value => {
  166. this.state.color.b = value / 255.0;
  167. this.forceUpdate();
  168. }}/>
  169. </div>
  170. <div className={"alpha" + (hasAlpha ? "" : " grayed")}>
  171. <ColorComponentEntry label="A" min={0} max={255} value={Math.round(this.state.alpha * 255)} onChange={value => {
  172. this.setState({alpha: value / 255.0});
  173. this.forceUpdate();
  174. }}/>
  175. </div>
  176. </div>
  177. <div className="color-picker-hex">
  178. <div className="color-picker-hex-label">
  179. Hex
  180. </div>
  181. <div className="color-picker-hex-value">
  182. <HexColor expectedLength={6} value={colorHex} onChange={value => {
  183. this.setState({color: Color3.FromHexString(value)});
  184. }}/>
  185. </div>
  186. </div>
  187. </div>
  188. );
  189. }
  190. }