Work around a WebKit bug in which the same path cannot be used to storke and fill.
[rgbconverter.git] / RGB Converter.wdgt / Widget.js
1 /*=====================================================================*\
2 || ###################################################################
3 || # RGB Converter
4 || # Copyright (c)2002-2010 Blue Static
5 || #
6 || # This program is free software; you can redistribute it and/or modify
7 || # it under the terms of the GNU General Public License as published by
8 || # the Free Software Foundation; version 2 of the License.
9 || #
10 || # This program is distributed in the hope that it will be useful, but
11 || # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 || # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 || # more details.
14 || #
15 || # You should have received a copy of the GNU General Public License along
16 || # with this program; if not, write to the Free Software Foundation, Inc.,
17 || # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
18 || ###################################################################
19 \*=====================================================================*/
20
21 var colors_ = {
22 red : 60,
23 green : 60,
24 blue : 60,
25 hex : '3C3C3C'
26 };
27
28 var backShowing_ = false;
29
30 // ###################################################################
31 // widget init
32 function Setup()
33 {
34 Draw();
35 createGenericButton(document.getElementById("backbutton"), "Done", hide_back, 0);
36 }
37
38 // ###################################################################
39 // watches the three RGB fields to make sure they don't go over the limites
40 function rgbwatcher(color)
41 {
42 field = document.getElementById(color + "input");
43
44 // handle RGB triads
45 if (triad = field.value.match(/(rgb)?\(?([0-9]{1,3}),\s?([0-9]{1,3}),\s?([0-9]{1,3})\)?/))
46 {
47 document.getElementById("redinput").value = triad[2];
48 document.getElementById("greeninput").value = triad[3];
49 document.getElementById("blueinput").value = triad[4];
50
51 rgbwatcher("red");
52 rgbwatcher("green");
53 rgbwatcher("blue");
54 return;
55 }
56
57 // sanitize the number
58 var newval = field.value.replace(/[^0-9\-\.]*/g, "");
59 newval = Math.floor(newval);
60
61 // make sure we don't go over 255
62 if (newval > 255)
63 {
64 newval = 255;
65 }
66 else if (newval < 0)
67 {
68 newval = 0;
69 }
70
71 // update the text field
72 field.value = newval;
73
74 // set fields[]
75 colors_[color] = newval;
76
77 // set hex
78 update_hex();
79
80 // redraw the swatch
81 Draw();
82 }
83
84 // ###################################################################
85 function Draw()
86 {
87 var canvas = document.getElementById('wheel');
88 var context = canvas.getContext('2d');
89 var radius = canvas.width / 2 - 3;
90 var center = [canvas.width / 2, canvas.height / 2];
91 context.strokeStyle = 'black';
92
93 context.clearRect(0, 0, canvas.width, canvas.height);
94
95 if (backShowing_) {
96 // Draw dark baground gradient.
97 context.beginPath();
98 context.arc(center[0], center[1], radius, 0, Math.PI * 2, true);
99 var gradient = context.createLinearGradient(0, 0, 0, canvas.height);
100 gradient.addColorStop(0, 'rgb(200, 200, 200)');
101 gradient.addColorStop(1, 'rgb(30, 30, 30)');
102 context.fillStyle = gradient;
103 context.fill();
104 context.closePath();
105 return;
106 }
107
108 // Draw the individual components. Start with red.
109 context.beginPath();
110 context.moveTo(center[0], center[1]);
111 context.arc(canvas.width / 2,
112 canvas.height / 2,
113 radius,
114 Math.PI * 4/3,
115 Math.PI * 6/3,
116 false);
117 context.lineTo(center[0], center[1]);
118 context.fillStyle = _GetComponentColorString('red');
119 context.fill();
120 context.stroke();
121
122 // Green component.
123 context.beginPath();
124 context.moveTo(center[0], center[1]);
125 context.arc(canvas.width / 2,
126 canvas.height / 2,
127 radius,
128 Math.PI * 0,
129 Math.PI * 2/3,
130 false);
131 context.lineTo(center[0], center[1]);
132 context.fillStyle = _GetComponentColorString('green');
133 context.fill();
134 context.stroke();
135
136 // Blue component.
137 context.beginPath();
138 context.moveTo(center[0], center[1]);
139 context.arc(canvas.width / 2,
140 canvas.height / 2,
141 radius,
142 Math.PI * 2/3,
143 Math.PI * 4/3,
144 false);
145 context.lineTo(center[0], center[1]);
146 context.fillStyle = _GetComponentColorString('blue');
147 context.fill();
148 context.stroke();
149
150 // Draw the sheen gradient.
151 context.beginPath();
152 context.arc(center[0], center[1], radius, 0, Math.PI * 2, true);
153 var gradient = context.createLinearGradient(0, 0, 0, canvas.height);
154 gradient.addColorStop(0, 'rgba(255,255,255,.5)');
155 gradient.addColorStop(1, 'rgba(0,0,0,0)');
156 context.fillStyle = gradient;
157 context.fill();
158 context.closePath();
159
160 // Draw the inner wheel. WebKit in 10.6.5 (and maybe earlier) has a bug in
161 // which you can't fill a path after stroking it, or vice versa.
162 var _arc = function(c) {
163 c.arc(center[0], center[1], canvas.width / 4.75, 0, Math.PI * 2, true);
164 }
165 context.save();
166 context.beginPath();
167 _arc(context)
168 context.strokeStyle = 'rgb(30,30,30)';
169 context.stroke();
170
171 context.beginPath();
172 _arc(context);
173 context.fillStyle = _GetRGBColorString();
174 context.fill();
175 context.closePath();
176 delete _arc;
177 }
178
179 // ###################################################################
180 // Returns the rgb(,,,) color string.
181
182 function _GetRGBColorString()
183 {
184 return 'rgb(' + colors_.red + ',' + colors_.green + ',' + colors_.blue + ')';
185 }
186
187 // ###################################################################
188 // Returns an individual color component's color string.
189
190 function _GetComponentColorString(color)
191 {
192 if (color == 'red') {
193 return 'rgb(' + colors_.red + ',0,0)';
194 } else if (color == 'green') {
195 return 'rgb(0,' + colors_.green + ',0)';
196 } else if (color == 'blue') {
197 return 'rgb(0,0,' + colors_.blue + ')';
198 }
199 alert('Invalid color ' + color);
200 }
201
202 // ###################################################################
203 // watches the hex field for updates
204 function hexwatcher()
205 {
206 field = document.getElementById("hexinput");
207
208 // sanitize the hex
209 var newval = field.value.replace(/[^0-9a-f]*/gi, "");
210
211 // get the length
212 var length = newval.length;
213
214 // make sure we're always 6
215 if (length > 6)
216 {
217 newval = newval.substr(0, 6);
218 }
219 else if (length == 3)
220 {
221 newval = newval.substr(0, 1) + newval.substr(0, 1) + newval.substr(1, 1) + newval.substr(1, 1) + newval.substr(2, 1) + newval.substr(2, 1);
222 }
223 else if (length < 6)
224 {
225 for (var i = length; i <= 6; i++)
226 {
227 newval = "" + newval + "0";
228 }
229 }
230
231 // update the field
232 field.value = newval;
233
234 // update fields[]
235 colors_.hex = newval;
236
237 // set RGB
238 update_rgb();
239
240 // redraw the swatch
241 Draw();
242 }
243
244 // ###################################################################
245 // update the hex value
246 function update_hex()
247 {
248 var hexstr = dec2hex(colors_.red) + dec2hex(colors_.green) + dec2hex(colors_.blue);
249 colors_.hex = hexstr;
250 document.getElementById("hexinput").value = hexstr;
251 }
252
253 // ###################################################################
254 // update the RGB values
255 function update_rgb()
256 {
257 // regex match the bits
258 var bits = colors_.hex.match(/(..)(..)(..)/);
259
260 colors_.red = hex2dec(bits[1]);
261 colors_.green = hex2dec(bits[2]);
262 colors_.blue = hex2dec(bits[3]);
263
264 // construct the hex values
265 document.getElementById("redinput").value = colors_.red;
266 document.getElementById("greeninput").value = colors_.green;
267 document.getElementById("blueinput").value = colors_.blue;
268 }
269
270 // ###################################################################
271 // convert a decimal to a hexidecimal
272 function dec2hex(dec)
273 {
274 var hexstr = "0123456789ABCDEF";
275 var low = dec % 16;
276 var high = (dec - low) / 16;
277 hex = "" + hexstr.charAt(high) + hexstr.charAt(low);
278
279 return hex.toString();
280 }
281
282 // ###################################################################
283 // converts a hexidecimal to a decimal
284 function hex2dec(hex)
285 {
286 return parseInt(hex, 16);
287 }
288
289 // ###################################################################
290 // ###################################################################
291 // ###################################################################
292
293 // flip data
294
295 function show_back()
296 {
297 backShowing_ = true;
298
299 var front = document.getElementById("front");
300 var back = document.getElementById("back");
301
302 if (window.widget)
303 {
304 widget.prepareForTransition("ToBack");
305 }
306
307 front.style.display = "none";
308 back.style.display = "block";
309
310 if (window.widget)
311 {
312 setTimeout("widget.performTransition();", 0);
313 }
314
315 document.getElementById("fliprollie").style.display = "none";
316
317 Draw();
318 }
319
320 function hide_back()
321 {
322 backShowing_ = false;
323
324 var front = document.getElementById("front");
325 var back = document.getElementById("back");
326
327 if (window.widget)
328 {
329 widget.prepareForTransition("ToFront");
330 }
331
332 back.style.display = "none";
333 front.style.display = "block";
334
335 if (window.widget)
336 {
337 setTimeout("widget.performTransition();", 0);
338 }
339
340 Draw();
341 }
342
343 var flipShown = false;
344
345 var animation = {
346 duration : 0,
347 starttime : 0,
348 to : 1.0,
349 now : 0.0,
350 from : 0.0,
351 firstElement : null,
352 timer : null
353 };
354
355 function mousemove(event)
356 {
357 if (backShowing_)
358 return;
359
360 if (!flipShown)
361 {
362 if (animation.timer != null)
363 {
364 clearInterval(animation.timer);
365 animation.timer = null;
366 }
367
368 var starttime = (new Date).getTime() - 13;
369
370 animation.duration = 500;
371 animation.starttime = starttime;
372 animation.firstElement = document.getElementById("flip");
373 animation.timer = setInterval("animate();", 13);
374 animation.from = animation.now;
375 animation.to = 1.0;
376 animate();
377 flipShown = true;
378 }
379 }
380
381 function mouseexit(event)
382 {
383 if (backShowing_)
384 return;
385
386 if (flipShown)
387 {
388 // fade in the flip widget
389 if (animation.timer != null)
390 {
391 clearInterval (animation.timer);
392 animation.timer = null;
393 }
394
395 var starttime = (new Date).getTime() - 13;
396
397 animation.duration = 500;
398 animation.starttime = starttime;
399 animation.firstElement = document.getElementById("flip");
400 animation.timer = setInterval("animate();", 13);
401 animation.from = animation.now;
402 animation.to = 0.0;
403 animate();
404 flipShown = false;
405 }
406 }
407
408
409 function animate()
410 {
411 var T;
412 var ease;
413 var time = (new Date).getTime();
414
415 T = limit_3(time - animation.starttime, 0, animation.duration);
416
417 if (T >= animation.duration)
418 {
419 clearInterval(animation.timer);
420 animation.timer = null;
421 animation.now = animation.to;
422 }
423 else
424 {
425 ease = 0.5 - (0.5 * Math.cos(Math.PI * T / animation.duration));
426 animation.now = compute_next_float(animation.from, animation.to, ease);
427 }
428
429 animation.firstElement.style.opacity = animation.now;
430 }
431
432 function limit_3 (a, b, c)
433 {
434 return a < b ? b : (a > c ? c : a);
435 }
436
437 function compute_next_float(from, to, ease)
438 {
439 return from + (to - from) * ease;
440 }
441
442 function enterflip(event)
443 {
444 document.getElementById("fliprollie").style.display = "block";
445 }
446
447 function exitflip(event)
448 {
449 document.getElementById("fliprollie").style.display = "none";
450 }