colorpicker.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  1. /// <reference path="../../../dist/preview release/babylon.d.ts"/>
  2. module BABYLON.GUI {
  3. export class ColorPicker extends Control {
  4. private _colorWheelCanvas: HTMLCanvasElement;
  5. private _value: Color3 = Color3.Red();
  6. private _tmpColor = new Color3();
  7. private _pointerStartedOnSquare = false;
  8. private _pointerStartedOnWheel = false;
  9. private _squareLeft = 0;
  10. private _squareTop = 0;
  11. private _squareSize = 0;
  12. private _h = 360;
  13. private _s = 1;
  14. private _v = 1;
  15. public onValueChangedObservable = new Observable<Color3>();
  16. public get value(): Color3 {
  17. return this._value;
  18. }
  19. public set value(value: Color3) {
  20. if (this._value.equals(value)) {
  21. return;
  22. }
  23. this._value.copyFrom(value);
  24. this._RGBtoHSV(this._value, this._tmpColor);
  25. this._h = this._tmpColor.r;
  26. this._s = Math.max(this._tmpColor.g, 0.00001);
  27. this._v = Math.max(this._tmpColor.b, 0.00001);
  28. this._markAsDirty();
  29. this.onValueChangedObservable.notifyObservers(this._value);
  30. }
  31. public set width(value: string | number ) {
  32. if (this._width.toString(this._host) === value) {
  33. return;
  34. }
  35. if (this._width.fromString(value)) {
  36. this._height.fromString(value);
  37. this._markAsDirty();
  38. }
  39. }
  40. public set height(value: string | number ) {
  41. if (this._height.toString(this._host) === value) {
  42. return;
  43. }
  44. if (this._height.fromString(value)) {
  45. this._width.fromString(value);
  46. this._markAsDirty();
  47. }
  48. }
  49. public get size(): string | number {
  50. return this.width;
  51. }
  52. public set size(value: string | number){
  53. this.width = value;
  54. }
  55. constructor(public name?: string) {
  56. super(name);
  57. this.value = new BABYLON.Color3(.88, .1, .1);
  58. this.size = "200px";
  59. this.isPointerBlocker = true;
  60. }
  61. protected _getTypeName(): string {
  62. return "ColorPicker";
  63. }
  64. private _updateSquareProps():void {
  65. var radius = Math.min(this._currentMeasure.width, this._currentMeasure.height)*.5;
  66. var wheelThickness = radius*.2;
  67. var innerDiameter = (radius-wheelThickness)*2;
  68. var squareSize = innerDiameter/(Math.sqrt(2));
  69. var offset = radius - squareSize*.5;
  70. this._squareLeft = this._currentMeasure.left + offset;
  71. this._squareTop = this._currentMeasure.top + offset;
  72. this._squareSize = squareSize;
  73. }
  74. private _drawGradientSquare(hueValue:number, left:number, top:number, width:number, height:number, context: CanvasRenderingContext2D) {
  75. var lgh = context.createLinearGradient(left, top, width+left, top);
  76. lgh.addColorStop(0, '#fff');
  77. lgh.addColorStop(1, 'hsl(' + hueValue + ', 100%, 50%)');
  78. context.fillStyle = lgh;
  79. context.fillRect(left, top, width, height);
  80. var lgv = context.createLinearGradient(left, top, left, height+top);
  81. lgv.addColorStop(0, 'rgba(0,0,0,0)');
  82. lgv.addColorStop(1, '#000');
  83. context.fillStyle = lgv;
  84. context.fillRect(left, top, width, height);
  85. }
  86. private _drawCircle(centerX:number, centerY:number, radius:number, context: CanvasRenderingContext2D) {
  87. context.beginPath();
  88. context.arc(centerX, centerY, radius+1, 0, 2 * Math.PI, false);
  89. context.lineWidth = 3;
  90. context.strokeStyle = '#333333';
  91. context.stroke();
  92. context.beginPath();
  93. context.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
  94. context.lineWidth = 3;
  95. context.strokeStyle = '#ffffff';
  96. context.stroke();
  97. }
  98. private _createColorWheelCanvas(radius:number, thickness:number): HTMLCanvasElement {
  99. var canvas = document.createElement("canvas");
  100. canvas.width = radius * 2;
  101. canvas.height = radius * 2;
  102. var context = <CanvasRenderingContext2D>canvas.getContext("2d");
  103. var image = context.getImageData(0, 0, radius * 2, radius * 2);
  104. var data = image.data;
  105. var color = this._tmpColor;
  106. var maxDistSq = radius*radius;
  107. var innerRadius = radius - thickness;
  108. var minDistSq = innerRadius*innerRadius;
  109. for (var x = -radius; x < radius; x++) {
  110. for (var y = -radius; y < radius; y++) {
  111. var distSq = x*x + y*y;
  112. if (distSq > maxDistSq || distSq < minDistSq) {
  113. continue;
  114. }
  115. var dist = Math.sqrt(distSq);
  116. var ang = Math.atan2(y, x);
  117. this._HSVtoRGB(ang * 180/Math.PI + 180, dist/radius, 1, color);
  118. var index = ((x + radius) + ((y + radius)*2*radius)) * 4;
  119. data[index] = color.r*255;
  120. data[index + 1] = color.g*255;
  121. data[index + 2] = color.b*255;
  122. var alphaRatio = (dist - innerRadius) / (radius - innerRadius);
  123. //apply less alpha to bigger color pickers
  124. var alphaAmount = .2;
  125. var maxAlpha = .2;
  126. var minAlpha = .04;
  127. var lowerRadius = 50;
  128. var upperRadius = 150;
  129. if(radius < lowerRadius){
  130. alphaAmount = maxAlpha;
  131. }else if(radius > upperRadius){
  132. alphaAmount = minAlpha;
  133. }else{
  134. alphaAmount = (minAlpha - maxAlpha)*(radius - lowerRadius)/(upperRadius - lowerRadius) + maxAlpha;
  135. }
  136. var alphaRatio = (dist - innerRadius) / (radius - innerRadius);
  137. if (alphaRatio < alphaAmount) {
  138. data[index + 3] = 255 * (alphaRatio / alphaAmount);
  139. } else if (alphaRatio > 1 - alphaAmount) {
  140. data[index + 3] = 255 * (1.0 - ((alphaRatio - (1 - alphaAmount)) / alphaAmount));
  141. } else {
  142. data[index + 3] = 255;
  143. }
  144. }
  145. }
  146. context.putImageData(image, 0, 0);
  147. return canvas;
  148. }
  149. private _RGBtoHSV(color:Color3, result:Color3){
  150. var r = color.r;
  151. var g = color.g;
  152. var b = color.b;
  153. var max = Math.max(r, g, b);
  154. var min = Math.min(r, g, b);
  155. var h = 0;
  156. var s = 0;
  157. var v = max;
  158. var dm = max - min;
  159. if(max !== 0){
  160. s = dm / max;
  161. }
  162. if(max != min) {
  163. if(max == r){
  164. h = (g - b) / dm;
  165. if(g < b){
  166. h += 6;
  167. }
  168. }else if(max == g){
  169. h = (b - r) / dm + 2;
  170. }else if(max == b){
  171. h = (r - g) / dm + 4;
  172. }
  173. h *= 60;
  174. }
  175. result.r = h;
  176. result.g = s;
  177. result.b = v;
  178. }
  179. private _HSVtoRGB(hue:number, saturation:number, value:number, result:Color3) {
  180. var chroma = value * saturation;
  181. var h = hue / 60;
  182. var x = chroma * (1- Math.abs((h % 2) - 1));
  183. var r = 0;
  184. var g = 0;
  185. var b = 0;
  186. if (h >= 0 && h <= 1) {
  187. r = chroma;
  188. g = x;
  189. } else if (h >= 1 && h <= 2) {
  190. r = x;
  191. g = chroma;
  192. } else if (h >= 2 && h <= 3) {
  193. g = chroma;
  194. b = x;
  195. } else if (h >= 3 && h <= 4) {
  196. g = x;
  197. b = chroma;
  198. } else if (h >= 4 && h <= 5) {
  199. r = x;
  200. b = chroma;
  201. } else if (h >= 5 && h <= 6) {
  202. r = chroma;
  203. b = x;
  204. }
  205. var m = value - chroma;
  206. result.set((r+m), (g+m), (b+m));
  207. }
  208. public _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
  209. context.save();
  210. this._applyStates(context);
  211. if (this._processMeasures(parentMeasure, context)) {
  212. var radius = Math.min(this._currentMeasure.width, this._currentMeasure.height)*.5;
  213. var wheelThickness = radius*.2;
  214. var left = this._currentMeasure.left;
  215. var top = this._currentMeasure.top;
  216. if(!this._colorWheelCanvas || this._colorWheelCanvas.width != radius*2){
  217. this._colorWheelCanvas = this._createColorWheelCanvas(radius, wheelThickness);
  218. }
  219. this._updateSquareProps();
  220. if(this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY){
  221. context.shadowColor = this.shadowColor;
  222. context.shadowBlur = this.shadowBlur;
  223. context.shadowOffsetX = this.shadowOffsetX;
  224. context.shadowOffsetY = this.shadowOffsetY;
  225. context.fillRect(this._squareLeft, this._squareTop, this._squareSize, this._squareSize);
  226. }
  227. context.drawImage(this._colorWheelCanvas, left, top);
  228. if(this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY){
  229. context.shadowBlur = 0;
  230. context.shadowOffsetX = 0;
  231. context.shadowOffsetY = 0;
  232. }
  233. this._drawGradientSquare(this._h,
  234. this._squareLeft,
  235. this._squareTop,
  236. this._squareSize,
  237. this._squareSize,
  238. context);
  239. var cx = this._squareLeft + this._squareSize*this._s;
  240. var cy = this._squareTop + this._squareSize*(1 - this._v);
  241. this._drawCircle(cx, cy, radius*.04, context);
  242. var dist = radius - wheelThickness*.5;
  243. cx = left + radius + Math.cos((this._h-180)*Math.PI/180)*dist;
  244. cy = top + radius + Math.sin((this._h-180)*Math.PI/180)*dist;
  245. this._drawCircle(cx, cy, wheelThickness*.35, context);
  246. }
  247. context.restore();
  248. }
  249. // Events
  250. private _pointerIsDown = false;
  251. private _updateValueFromPointer(x: number, y:number): void {
  252. if(this._pointerStartedOnWheel)
  253. {
  254. var radius = Math.min(this._currentMeasure.width, this._currentMeasure.height)*.5;
  255. var centerX = radius + this._currentMeasure.left;
  256. var centerY = radius + this._currentMeasure.top;
  257. this._h = Math.atan2(y - centerY, x - centerX)*180/Math.PI + 180;
  258. }
  259. else if(this._pointerStartedOnSquare)
  260. {
  261. this._updateSquareProps();
  262. this._s = (x - this._squareLeft) / this._squareSize;
  263. this._v = 1 - (y - this._squareTop) / this._squareSize;
  264. this._s = Math.min(this._s, 1);
  265. this._s = Math.max(this._s, 0.00001);
  266. this._v = Math.min(this._v, 1);
  267. this._v = Math.max(this._v, 0.00001);
  268. }
  269. this._HSVtoRGB(this._h, this._s, this._v, this._tmpColor);
  270. this.value = this._tmpColor;
  271. }
  272. private _isPointOnSquare(coordinates: Vector2):boolean {
  273. this._updateSquareProps();
  274. var left = this._squareLeft;
  275. var top = this._squareTop;
  276. var size = this._squareSize;
  277. if(coordinates.x >= left && coordinates.x <= left + size &&
  278. coordinates.y >= top && coordinates.y <= top + size){
  279. return true;
  280. }
  281. return false;
  282. }
  283. private _isPointOnWheel(coordinates: Vector2):boolean {
  284. var radius = Math.min(this._currentMeasure.width, this._currentMeasure.height)*.5;
  285. var centerX = radius + this._currentMeasure.left;
  286. var centerY = radius + this._currentMeasure.top;
  287. var wheelThickness = radius*.2;
  288. var innerRadius = radius-wheelThickness;
  289. var radiusSq = radius*radius;
  290. var innerRadiusSq = innerRadius*innerRadius;
  291. var dx = coordinates.x - centerX;
  292. var dy = coordinates.y - centerY;
  293. var distSq = dx*dx + dy*dy;
  294. if(distSq <= radiusSq && distSq >= innerRadiusSq){
  295. return true;
  296. }
  297. return false;
  298. }
  299. public _onPointerDown(target: Control, coordinates: Vector2, pointerId:number, buttonIndex: number): boolean {
  300. if (!super._onPointerDown(target, coordinates, pointerId, buttonIndex)) {
  301. return false;
  302. }
  303. this._pointerIsDown = true;
  304. this._pointerStartedOnSquare = false;
  305. this._pointerStartedOnWheel = false;
  306. if(this._isPointOnSquare(coordinates)){
  307. this._pointerStartedOnSquare = true;
  308. }else if(this._isPointOnWheel(coordinates)){
  309. this._pointerStartedOnWheel = true;
  310. }
  311. this._updateValueFromPointer(coordinates.x, coordinates.y);
  312. this._host._capturingControl[pointerId] = this;
  313. return true;
  314. }
  315. public _onPointerMove(target: Control, coordinates: Vector2): void {
  316. if (this._pointerIsDown) {
  317. this._updateValueFromPointer(coordinates.x, coordinates.y);
  318. }
  319. super._onPointerMove(target, coordinates);
  320. }
  321. public _onPointerUp (target: Control, coordinates: Vector2, pointerId:number, buttonIndex: number, notifyClick: boolean): void {
  322. this._pointerIsDown = false;
  323. delete this._host._capturingControl[pointerId];
  324. super._onPointerUp(target, coordinates, pointerId, buttonIndex, notifyClick);
  325. }
  326. }
  327. }