OSDN Git Service

923a027d2b3e41b107619bdbda3f6b853450e9c5
[android-x86/packages-apps-ConnectBot.git] / src / org / connectbot / util / UberColorPickerDialog.java
1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /*
18  * 090408
19  * Keith Wiley
20  * kwiley@keithwiley.com
21  * http://keithwiley.com
22  *
23  * UberColorPickerDialog v1.1
24  *
25  * This color picker was implemented as a (significant) extension of the
26  * ColorPickerDialog class provided in the Android API Demos.  You are free
27  * to drop it unchanged into your own projects or to modify it as you see
28  * fit.  I would appreciate it if this comment block were let intact,
29  * merely for credit's sake.
30  *
31  * Enjoy!
32  */
33
34 package org.connectbot.util;
35
36 import android.app.Dialog;
37 import android.content.Context;
38 import android.graphics.Bitmap;
39 import android.graphics.Canvas;
40 import android.graphics.Color;
41 import android.graphics.ColorMatrix;
42 import android.graphics.ComposeShader;
43 import android.graphics.Paint;
44 import android.graphics.PorterDuff;
45 import android.graphics.PorterDuffXfermode;
46 import android.graphics.RadialGradient;
47 import android.graphics.Rect;
48 import android.graphics.RectF;
49 import android.graphics.Shader;
50 import android.graphics.SweepGradient;
51 import android.graphics.drawable.GradientDrawable;
52 import android.graphics.drawable.GradientDrawable.Orientation;
53 import android.os.Bundle;
54 import android.util.DisplayMetrics;
55 import android.view.MotionEvent;
56 import android.view.View;
57
58 /**
59  * UberColorPickerDialog is a seriously enhanced version of the UberColorPickerDialog
60  * class provided in the Android API Demos.<p>
61  *
62  * NOTE (from Kenny Root): This is a VERY slimmed down version custom for ConnectBot.
63  * Visit Keith's site for the full version at the URL listed in the author line.<p>
64  *
65  * @author Keith Wiley, kwiley@keithwiley.com, http://keithwiley.com
66  */
67 public class UberColorPickerDialog extends Dialog {
68         private OnColorChangedListener mListener;
69         private int mInitialColor;
70
71         /**
72          * Callback to the creator of the dialog, informing the creator of a new color and notifying that the dialog is about to dismiss.
73          */
74         public interface OnColorChangedListener {
75                 void colorChanged(int color);
76         }
77
78         /**
79          * Ctor
80          * @param context
81          * @param listener
82          * @param initialColor
83          * @param showTitle If true, a title is shown across the top of the dialog.  If false a toast is shown instead.
84          */
85         public UberColorPickerDialog(Context context,
86                                                         OnColorChangedListener listener,
87                                                         int initialColor) {
88                 super(context);
89
90                 mListener = listener;
91                 mInitialColor = initialColor;
92         }
93
94         /**
95          * Activity entry point
96          */
97         @Override
98         protected void onCreate(Bundle savedInstanceState) {
99                 super.onCreate(savedInstanceState);
100                 OnColorChangedListener l = new OnColorChangedListener() {
101                         public void colorChanged(int color) {
102                                 mListener.colorChanged(color);
103                                 dismiss();
104                         }
105                 };
106
107                 DisplayMetrics dm = new DisplayMetrics();
108                 getWindow().getWindowManager().getDefaultDisplay().getMetrics(dm);
109                 int screenWidth = dm.widthPixels;
110                 int screenHeight = dm.heightPixels;
111
112                 setTitle("Pick a color (try the trackball)");
113
114                 try {
115                         setContentView(new ColorPickerView(getContext(), l, screenWidth, screenHeight, mInitialColor));
116                 }
117                 catch (Exception e) {
118                         //There is currently only one kind of ctor exception, that where no methods are enabled.
119                         dismiss();      //This doesn't work!  The dialog is still shown (its title at least, the layout is empty from the exception being thrown).  <sigh>
120                 }
121         }
122
123         /**
124          * ColorPickerView is the meat of this color picker (as opposed to the enclosing class).
125          * All the heavy lifting is done directly by this View subclass.
126          * <P>
127          * You can enable/disable whichever color chooser methods you want by modifying the ENABLED_METHODS switches.  They *should*
128          * do all the work required to properly enable/disable methods without losing track of what goes with what and what maps to what.
129          * <P>
130          * If you add a new color chooser method, do a text search for "NEW_METHOD_WORK_NEEDED_HERE".  That tag indicates all
131          * the locations in the code that will have to be amended in order to properly add a new color chooser method.
132          * I highly recommend adding new methods to the end of the list.  If you want to try to reorder the list, you're on your own.
133          */
134         private static class ColorPickerView extends View {
135                 private static int SWATCH_WIDTH = 95;
136                 private static final int SWATCH_HEIGHT = 60;
137
138                 private static int PALETTE_POS_X = 0;
139                 private static int PALETTE_POS_Y = SWATCH_HEIGHT;
140                 private static final int PALETTE_DIM = SWATCH_WIDTH * 2;
141                 private static final int PALETTE_RADIUS = PALETTE_DIM / 2;
142                 private static final int PALETTE_CENTER_X = PALETTE_RADIUS;
143                 private static final int PALETTE_CENTER_Y = PALETTE_RADIUS;
144
145                 private static final int SLIDER_THICKNESS = 40;
146
147                 private static int VIEW_DIM_X = PALETTE_DIM;
148                 private static int VIEW_DIM_Y = SWATCH_HEIGHT;
149
150                 //NEW_METHOD_WORK_NEEDED_HERE
151                 private static final int METHOD_HS_V_PALETTE = 0;
152
153                 //NEW_METHOD_WORK_NEEDED_HERE
154                 //Add a new entry to the list for each controller in the new method
155                 private static final int TRACKED_NONE = -1;     //No object on screen is currently being tracked
156                 private static final int TRACK_SWATCH_OLD = 10;
157                 private static final int TRACK_SWATCH_NEW = 11;
158                 private static final int TRACK_HS_PALETTE = 30;
159                 private static final int TRACK_VER_VALUE_SLIDER = 31;
160
161                 private static final int TEXT_SIZE = 12;
162                 private static int[] TEXT_HSV_POS = new int[2];
163                 private static int[] TEXT_RGB_POS = new int[2];
164                 private static int[] TEXT_YUV_POS = new int[2];
165                 private static int[] TEXT_HEX_POS = new int[2];
166
167                 private static final float PI = 3.141592653589793f;
168
169                 private int mMethod = METHOD_HS_V_PALETTE;
170                 private int mTracking = TRACKED_NONE;   //What object on screen is currently being tracked for movement
171
172                 //Zillions of persistant Paint objecs for drawing the View
173
174                 private Paint mSwatchOld, mSwatchNew;
175
176                 //NEW_METHOD_WORK_NEEDED_HERE
177                 //Add Paints to represent the palettes of the new method's UI controllers
178                 private Paint mOvalHueSat;
179
180                 private Bitmap mVerSliderBM;
181                 private Canvas mVerSliderCv;
182
183                 private Bitmap[] mHorSlidersBM = new Bitmap[3];
184                 private Canvas[] mHorSlidersCv = new Canvas[3];
185
186                 private Paint mValDimmer;
187
188                 //NEW_METHOD_WORK_NEEDED_HERE
189                 //Add Paints to represent the icon for the new method
190                 private Paint mOvalHueSatSmall;
191
192                 private Paint mPosMarker;
193                 private Paint mText;
194
195                 private Rect mOldSwatchRect = new Rect();
196                 private Rect mNewSwatchRect = new Rect();
197                 private Rect mPaletteRect = new Rect();
198                 private Rect mVerSliderRect = new Rect();
199
200                 private int[] mSpectrumColorsRev;
201                 private int mOriginalColor = 0; //The color passed in at the beginning, which can be reverted to at any time by tapping the old swatch.
202                 private float[] mHSV = new float[3];
203                 private int[] mRGB = new int[3];
204                 private float[] mYUV = new float[3];
205                 private String mHexStr = "";
206                 private boolean mHSVenabled = true;     //Only true if an HSV method is enabled
207                 private boolean mRGBenabled = true;     //Only true if an RGB method is enabled
208                 private boolean mYUVenabled = true;     //Only true if a YUV method is enabled
209                 private boolean mHexenabled = true;     //Only true if an RGB method is enabled
210                 private int[] mCoord = new int[3];              //For drawing slider/palette markers
211                 private int mFocusedControl = -1;       //Which control receives trackball events.
212                 private OnColorChangedListener mListener;
213
214                 /**
215                  * Ctor.
216                  * @param c
217                  * @param l
218                  * @param width Used to determine orientation and adjust layout accordingly
219                  * @param height Used to determine orientation and adjust layout accordingly
220                  * @param color The initial color
221                  * @throws Exception
222                  */
223                 ColorPickerView(Context c, OnColorChangedListener l, int width, int height, int color)
224                 throws Exception {
225                         super(c);
226
227                         //We need to make the dialog focusable to retrieve trackball events.
228                         setFocusable(true);
229
230                         mListener = l;
231
232                         mOriginalColor = color;
233
234                         Color.colorToHSV(color, mHSV);
235
236                         updateAllFromHSV();
237
238                         //Setup the layout based on whether this is a portrait or landscape orientation.
239                         if (width <= height) {  //Portrait layout
240                                 SWATCH_WIDTH = (PALETTE_DIM + SLIDER_THICKNESS) / 2;
241
242                                 PALETTE_POS_X = 0;
243                                 PALETTE_POS_Y = TEXT_SIZE * 4 + SWATCH_HEIGHT;
244
245                                 //Set more rects, lots of rects
246                                 mOldSwatchRect.set(0, TEXT_SIZE * 4, SWATCH_WIDTH, TEXT_SIZE * 4 + SWATCH_HEIGHT);
247                                 mNewSwatchRect.set(SWATCH_WIDTH, TEXT_SIZE * 4, SWATCH_WIDTH * 2, TEXT_SIZE * 4 + SWATCH_HEIGHT);
248                                 mPaletteRect.set(0, PALETTE_POS_Y, PALETTE_DIM, PALETTE_POS_Y + PALETTE_DIM);
249                                 mVerSliderRect.set(PALETTE_DIM, PALETTE_POS_Y, PALETTE_DIM + SLIDER_THICKNESS, PALETTE_POS_Y + PALETTE_DIM);
250
251                                 TEXT_HSV_POS[0] = 3;
252                                 TEXT_HSV_POS[1] = 0;
253                                 TEXT_RGB_POS[0] = TEXT_HSV_POS[0] + 50;
254                                 TEXT_RGB_POS[1] = TEXT_HSV_POS[1];
255                                 TEXT_YUV_POS[0] = TEXT_HSV_POS[0] + 100;
256                                 TEXT_YUV_POS[1] = TEXT_HSV_POS[1];
257                                 TEXT_HEX_POS[0] = TEXT_HSV_POS[0] + 150;
258                                 TEXT_HEX_POS[1] = TEXT_HSV_POS[1];
259
260                                 VIEW_DIM_X = PALETTE_DIM + SLIDER_THICKNESS;
261                                 VIEW_DIM_Y = SWATCH_HEIGHT + PALETTE_DIM + TEXT_SIZE * 4;
262                         }
263                         else {  //Landscape layout
264                                 SWATCH_WIDTH = 110;
265
266                                 PALETTE_POS_X = SWATCH_WIDTH;
267                                 PALETTE_POS_Y = 0;
268
269                                 //Set more rects, lots of rects
270                                 mOldSwatchRect.set(0, TEXT_SIZE * 7, SWATCH_WIDTH, TEXT_SIZE * 7 + SWATCH_HEIGHT);
271                                 mNewSwatchRect.set(0, TEXT_SIZE * 7 + SWATCH_HEIGHT, SWATCH_WIDTH, TEXT_SIZE * 7 + SWATCH_HEIGHT * 2);
272                                 mPaletteRect.set(SWATCH_WIDTH, PALETTE_POS_Y, SWATCH_WIDTH + PALETTE_DIM, PALETTE_POS_Y + PALETTE_DIM);
273                                 mVerSliderRect.set(SWATCH_WIDTH + PALETTE_DIM, PALETTE_POS_Y, SWATCH_WIDTH + PALETTE_DIM + SLIDER_THICKNESS, PALETTE_POS_Y + PALETTE_DIM);
274
275                                 TEXT_HSV_POS[0] = 3;
276                                 TEXT_HSV_POS[1] = 0;
277                                 TEXT_RGB_POS[0] = TEXT_HSV_POS[0];
278                                 TEXT_RGB_POS[1] = (int)(TEXT_HSV_POS[1] + TEXT_SIZE * 3.5);
279                                 TEXT_YUV_POS[0] = TEXT_HSV_POS[0] + 50;
280                                 TEXT_YUV_POS[1] = (int)(TEXT_HSV_POS[1] + TEXT_SIZE * 3.5);
281                                 TEXT_HEX_POS[0] = TEXT_HSV_POS[0] + 50;
282                                 TEXT_HEX_POS[1] = TEXT_HSV_POS[1];
283
284                                 VIEW_DIM_X = PALETTE_POS_X + PALETTE_DIM + SLIDER_THICKNESS;
285                                 VIEW_DIM_Y = Math.max(mNewSwatchRect.bottom, PALETTE_DIM);
286                         }
287
288                         //Rainbows make everybody happy!
289                         mSpectrumColorsRev = new int[] {
290                                 0xFFFF0000, 0xFFFF00FF, 0xFF0000FF, 0xFF00FFFF,
291                                 0xFF00FF00, 0xFFFFFF00, 0xFFFF0000,
292                         };
293
294                         //Setup all the Paint and Shader objects.  There are lots of them!
295
296                         //NEW_METHOD_WORK_NEEDED_HERE
297                         //Add Paints to represent the palettes of the new method's UI controllers
298
299                         mSwatchOld = new Paint(Paint.ANTI_ALIAS_FLAG);
300                         mSwatchOld.setStyle(Paint.Style.FILL);
301                         mSwatchOld.setColor(Color.HSVToColor(mHSV));
302
303                         mSwatchNew = new Paint(Paint.ANTI_ALIAS_FLAG);
304                         mSwatchNew.setStyle(Paint.Style.FILL);
305                         mSwatchNew.setColor(Color.HSVToColor(mHSV));
306
307                         Shader shaderA = new SweepGradient(0, 0, mSpectrumColorsRev, null);
308                         Shader shaderB = new RadialGradient(0, 0, PALETTE_CENTER_X, 0xFFFFFFFF, 0xFF000000, Shader.TileMode.CLAMP);
309                         Shader shader = new ComposeShader(shaderA, shaderB, PorterDuff.Mode.SCREEN);
310                         mOvalHueSat = new Paint(Paint.ANTI_ALIAS_FLAG);
311                         mOvalHueSat.setShader(shader);
312                         mOvalHueSat.setStyle(Paint.Style.FILL);
313                         mOvalHueSat.setDither(true);
314
315                         mVerSliderBM = Bitmap.createBitmap(SLIDER_THICKNESS, PALETTE_DIM, Bitmap.Config.RGB_565);
316                         mVerSliderCv = new Canvas(mVerSliderBM);
317
318                         for (int i = 0; i < 3; i++) {
319                                 mHorSlidersBM[i] = Bitmap.createBitmap(PALETTE_DIM, SLIDER_THICKNESS, Bitmap.Config.RGB_565);
320                                 mHorSlidersCv[i] = new Canvas(mHorSlidersBM[i]);
321                         }
322
323                         mValDimmer = new Paint(Paint.ANTI_ALIAS_FLAG);
324                         mValDimmer.setStyle(Paint.Style.FILL);
325                         mValDimmer.setDither(true);
326                         mValDimmer.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY));
327
328                         //Whew, we're done making the big Paints and Shaders for the swatches, palettes, and sliders.
329                         //Now we need to make the Paints and Shaders that will draw the little method icons in the method selector list.
330
331                         //NEW_METHOD_WORK_NEEDED_HERE
332                         //Add Paints to represent the icon for the new method
333
334                         shaderA = new SweepGradient(0, 0, mSpectrumColorsRev, null);
335                         shaderB = new RadialGradient(0, 0, PALETTE_DIM / 2, 0xFFFFFFFF, 0xFF000000, Shader.TileMode.CLAMP);
336                         shader = new ComposeShader(shaderA, shaderB, PorterDuff.Mode.SCREEN);
337                         mOvalHueSatSmall = new Paint(Paint.ANTI_ALIAS_FLAG);
338                         mOvalHueSatSmall.setShader(shader);
339                         mOvalHueSatSmall.setStyle(Paint.Style.FILL);
340
341                         //Make a simple stroking Paint for drawing markers and borders and stuff like that.
342                         mPosMarker = new Paint(Paint.ANTI_ALIAS_FLAG);
343                         mPosMarker.setStyle(Paint.Style.STROKE);
344                         mPosMarker.setStrokeWidth(2);
345
346                         //Make a basic text Paint.
347                         mText = new Paint(Paint.ANTI_ALIAS_FLAG);
348                         mText.setTextSize(TEXT_SIZE);
349                         mText.setColor(Color.WHITE);
350
351                         //Kickstart
352                         initUI();
353                 }
354
355                 /**
356                  * Draw the entire view (the entire dialog).
357                  */
358                 @Override
359                 protected void onDraw(Canvas canvas) {
360                         //Draw the old and new swatches
361                         drawSwatches(canvas);
362
363                         //Write the text
364                         writeColorParams(canvas);
365
366                         //Draw the palette and sliders (the UI)
367                         if (mMethod == METHOD_HS_V_PALETTE)
368                                 drawHSV1Palette(canvas);
369                 }
370
371                 /**
372                  * Draw the old and new swatches.
373                  * @param canvas
374                  */
375                 private void drawSwatches(Canvas canvas) {
376                         float[] hsv = new float[3];
377
378                         mText.setTextSize(16);
379
380                         //Draw the original swatch
381                         canvas.drawRect(mOldSwatchRect, mSwatchOld);
382                         Color.colorToHSV(mOriginalColor, hsv);
383                         //if (UberColorPickerDialog.isGray(mColor))     //Don't need this right here, but imp't to note
384                         //      hsv[1] = 0;
385                         if (hsv[2] > .5)
386                                 mText.setColor(Color.BLACK);
387                         canvas.drawText("Revert", mOldSwatchRect.left + SWATCH_WIDTH / 2 - mText.measureText("Revert") / 2, mOldSwatchRect.top + 16, mText);
388                         mText.setColor(Color.WHITE);
389
390                         //Draw the new swatch
391                         canvas.drawRect(mNewSwatchRect, mSwatchNew);
392                         if (mHSV[2] > .5)
393                                 mText.setColor(Color.BLACK);
394                         canvas.drawText("Accept", mNewSwatchRect.left + SWATCH_WIDTH / 2 - mText.measureText("Accept") / 2, mNewSwatchRect.top + 16, mText);
395                         mText.setColor(Color.WHITE);
396
397                         mText.setTextSize(TEXT_SIZE);
398                 }
399
400                 /**
401                  * Write the color parametes (HSV, RGB, YUV, Hex, etc.).
402                  * @param canvas
403                  */
404                 private void writeColorParams(Canvas canvas) {
405                         if (mHSVenabled) {
406                                 canvas.drawText("H: " + Integer.toString((int)(mHSV[0] / 360.0f * 255)), TEXT_HSV_POS[0], TEXT_HSV_POS[1] + TEXT_SIZE, mText);
407                                 canvas.drawText("S: " + Integer.toString((int)(mHSV[1] * 255)), TEXT_HSV_POS[0], TEXT_HSV_POS[1] + TEXT_SIZE * 2, mText);
408                                 canvas.drawText("V: " + Integer.toString((int)(mHSV[2] * 255)), TEXT_HSV_POS[0], TEXT_HSV_POS[1] + TEXT_SIZE * 3, mText);
409                         }
410
411                         if (mRGBenabled) {
412                                 canvas.drawText("R: " + mRGB[0], TEXT_RGB_POS[0], TEXT_RGB_POS[1] + TEXT_SIZE, mText);
413                                 canvas.drawText("G: " + mRGB[1], TEXT_RGB_POS[0], TEXT_RGB_POS[1] + TEXT_SIZE * 2, mText);
414                                 canvas.drawText("B: " + mRGB[2], TEXT_RGB_POS[0], TEXT_RGB_POS[1] + TEXT_SIZE * 3, mText);
415                         }
416
417                         if (mYUVenabled) {
418                                 canvas.drawText("Y: " + Integer.toString((int)(mYUV[0] * 255)), TEXT_YUV_POS[0], TEXT_YUV_POS[1] + TEXT_SIZE, mText);
419                                 canvas.drawText("U: " + Integer.toString((int)((mYUV[1] + .5f) * 255)), TEXT_YUV_POS[0], TEXT_YUV_POS[1] + TEXT_SIZE * 2, mText);
420                                 canvas.drawText("V: " + Integer.toString((int)((mYUV[2] + .5f) * 255)), TEXT_YUV_POS[0], TEXT_YUV_POS[1] + TEXT_SIZE * 3, mText);
421                         }
422
423                         if (mHexenabled)
424                                 canvas.drawText("#" + mHexStr, TEXT_HEX_POS[0], TEXT_HEX_POS[1] + TEXT_SIZE, mText);
425                 }
426
427                 /**
428                  * Place a small circle on the 2D palette to indicate the current values.
429                  * @param canvas
430                  * @param markerPosX
431                  * @param markerPosY
432                  */
433                 private void mark2DPalette(Canvas canvas, int markerPosX, int markerPosY) {
434                         mPosMarker.setColor(Color.BLACK);
435                         canvas.drawOval(new RectF(markerPosX - 5, markerPosY - 5, markerPosX + 5, markerPosY + 5), mPosMarker);
436                         mPosMarker.setColor(Color.WHITE);
437                         canvas.drawOval(new RectF(markerPosX - 3, markerPosY - 3, markerPosX + 3, markerPosY + 3), mPosMarker);
438                 }
439
440                 /**
441                  * Draw a line across the slider to indicate its current value.
442                  * @param canvas
443                  * @param markerPos
444                  */
445                 private void markVerSlider(Canvas canvas, int markerPos) {
446                         mPosMarker.setColor(Color.BLACK);
447                         canvas.drawRect(new Rect(0, markerPos - 2, SLIDER_THICKNESS, markerPos + 3), mPosMarker);
448                         mPosMarker.setColor(Color.WHITE);
449                         canvas.drawRect(new Rect(0, markerPos, SLIDER_THICKNESS, markerPos + 1), mPosMarker);
450                 }
451
452                 /**
453                  * Frame the slider to indicate that it has trackball focus.
454                  * @param canvas
455                  */
456                 private void hilightFocusedVerSlider(Canvas canvas) {
457                         mPosMarker.setColor(Color.WHITE);
458                         canvas.drawRect(new Rect(0, 0, SLIDER_THICKNESS, PALETTE_DIM), mPosMarker);
459                         mPosMarker.setColor(Color.BLACK);
460                         canvas.drawRect(new Rect(2, 2, SLIDER_THICKNESS - 2, PALETTE_DIM - 2), mPosMarker);
461                 }
462
463                 /**
464                  * Frame the 2D palette to indicate that it has trackball focus.
465                  * @param canvas
466                  */
467                 private void hilightFocusedOvalPalette(Canvas canvas) {
468                         mPosMarker.setColor(Color.WHITE);
469                         canvas.drawOval(new RectF(-PALETTE_RADIUS, -PALETTE_RADIUS, PALETTE_RADIUS, PALETTE_RADIUS), mPosMarker);
470                         mPosMarker.setColor(Color.BLACK);
471                         canvas.drawOval(new RectF(-PALETTE_RADIUS + 2, -PALETTE_RADIUS + 2, PALETTE_RADIUS - 2, PALETTE_RADIUS - 2), mPosMarker);
472                 }
473
474                 //NEW_METHOD_WORK_NEEDED_HERE
475                 //To add a new method, replicate the basic draw functions here.  Use the 2D palette or 1D sliders as templates for the new method.
476                 /**
477                  * Draw the UI for HSV with angular H and radial S combined in 2D and a 1D V slider.
478                  * @param canvas
479                  */
480                 private void drawHSV1Palette(Canvas canvas) {
481                         canvas.save();
482
483                         canvas.translate(PALETTE_POS_X, PALETTE_POS_Y);
484
485                         //Draw the 2D palette
486                         canvas.translate(PALETTE_CENTER_X, PALETTE_CENTER_Y);
487                         canvas.drawOval(new RectF(-PALETTE_RADIUS, -PALETTE_RADIUS, PALETTE_RADIUS, PALETTE_RADIUS), mOvalHueSat);
488                         canvas.drawOval(new RectF(-PALETTE_RADIUS, -PALETTE_RADIUS, PALETTE_RADIUS, PALETTE_RADIUS), mValDimmer);
489                         if (mFocusedControl == 0)
490                                 hilightFocusedOvalPalette(canvas);
491                         mark2DPalette(canvas, mCoord[0], mCoord[1]);
492                         canvas.translate(-PALETTE_CENTER_X, -PALETTE_CENTER_Y);
493
494                         //Draw the 1D slider
495                         canvas.translate(PALETTE_DIM, 0);
496                         canvas.drawBitmap(mVerSliderBM, 0, 0, null);
497                         if (mFocusedControl == 1)
498                                 hilightFocusedVerSlider(canvas);
499                         markVerSlider(canvas, mCoord[2]);
500
501                         canvas.restore();
502                 }
503
504                 /**
505                  * Initialize the current color chooser's UI (set its color parameters and set its palette and slider values accordingly).
506                  */
507                 private void initUI() {
508                         initHSV1Palette();
509
510                         //Focus on the first controller (arbitrary).
511                         mFocusedControl = 0;
512                 }
513
514                 //NEW_METHOD_WORK_NEEDED_HERE
515                 //To add a new method, replicate and extend the last init function shown below
516                 /**
517                  * Initialize a color chooser.
518                  */
519                 private void initHSV1Palette() {
520                         setOvalValDimmer();
521                         setVerValSlider();
522
523                         float angle = 2*PI - mHSV[0] / (180 / 3.1415927f);
524                         float radius = mHSV[1] * PALETTE_RADIUS;
525                         mCoord[0] = (int)(Math.cos(angle) * radius);
526                         mCoord[1] = (int)(Math.sin(angle) * radius);
527
528                         mCoord[2] = PALETTE_DIM - (int)(mHSV[2] * PALETTE_DIM);
529                 }
530
531                 //NEW_METHOD_WORK_NEEDED_HERE
532                 //To add a new method, replicate and extend the set functions below, one per UI controller in the new method
533                 /**
534                  * Adjust a Paint which, when painted, dims its underlying object to show the effects of varying value (brightness).
535                  */
536                 private void setOvalValDimmer() {
537                         float[] hsv = new float[3];
538                         hsv[0] = mHSV[0];
539                         hsv[1] = 0;
540                         hsv[2] = mHSV[2];
541                         int gray = Color.HSVToColor(hsv);
542                         mValDimmer.setColor(gray);
543                 }
544
545                 /**
546                  * Create a linear gradient shader to show variations in value.
547                  */
548                 private void setVerValSlider() {
549                         float[] hsv = new float[3];
550                         hsv[0] = mHSV[0];
551                         hsv[1] = mHSV[1];
552                         hsv[2] = 1;
553                         int col = Color.HSVToColor(hsv);
554
555                         int colors[] = new int[2];
556                         colors[0] = col;
557                         colors[1] = 0xFF000000;
558                         GradientDrawable gradDraw = new GradientDrawable(Orientation.TOP_BOTTOM, colors);
559                         gradDraw.setDither(true);
560                         gradDraw.setLevel(10000);
561                         gradDraw.setBounds(0, 0, SLIDER_THICKNESS, PALETTE_DIM);
562                         gradDraw.draw(mVerSliderCv);
563                 }
564
565                 /**
566                  * Report the correct tightly bounded dimensions of the view.
567                  */
568                 @Override
569                 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
570                         setMeasuredDimension(VIEW_DIM_X, VIEW_DIM_Y);
571                 }
572
573                 /**
574                  * Convert a slider position in the range [0,PALETTE_DIM] to a byte value in the range [0,255].
575                  * @param sliderPos in the range [0,PALETTE_DIM].
576                  * @return
577                  */
578                 public int sliderPosTo255(int sliderPos) {
579                         int int255 = (int)(255.0f * ((float)sliderPos / (float)PALETTE_DIM));
580                         int255 = pinToByte(int255);
581                         return int255;
582                 }
583
584                 /**
585                  * Wrap Math.round().  I'm not a Java expert.  Is this the only way to avoid writing "(int)Math.round" everywhere?
586                  * @param x
587                  * @return
588                  */
589                 private int round(double x) {
590                         return (int)Math.round(x);
591                 }
592
593                 /**
594                  * Limit a value to a min and max range.
595                  * @param n
596                  * @return
597                  */
598                 private int pinToByte(int n) {
599                         if (n < 0) {
600                                 n = 0;
601                         } else if (n > 255) {
602                                 n = 255;
603                         }
604                         return n;
605                 }
606
607                 /**
608                  * Limit a value to the range [0,1].
609                  * @param n
610                  * @return
611                  */
612                 private float pinToUnit(float n) {
613                         if (n < 0) {
614                                 n = 0;
615                         } else if (n > 1) {
616                                 n = 1;
617                         }
618                         return n;
619                 }
620
621                 /**
622                  * Limit a value to the range [0,max].
623                  * @param n
624                  * @param max
625                  * @return
626                  */
627                 private float pin(float n, float max) {
628                         if (n < 0) {
629                                 n = 0;
630                         } else if (n > max) {
631                                 n = max;
632                         }
633                         return n;
634                 }
635
636                 /**
637                  * Limit a value to the range [min,max].
638                  * @param n
639                  * @param min
640                  * @param max
641                  * @return
642                  */
643                 private float pin(float n, float min, float max) {
644                         if (n < min) {
645                                 n = min;
646                         } else if (n > max) {
647                                 n = max;
648                         }
649                         return n;
650                 }
651
652                 /**
653                  * No clue what this does (some sort of average/mean I presume).  It came with the original UberColorPickerDialog
654                  * in the API Demos and wasn't documented.  I don't feel like spending any time figuring it out, I haven't looked at it at all.
655                  * @param s
656                  * @param d
657                  * @param p
658                  * @return
659                  */
660                 private int ave(int s, int d, float p) {
661                         return s + round(p * (d - s));
662                 }
663
664                 /**
665                  * Came with the original UberColorPickerDialog in the API Demos, wasn't documented.  I believe it takes an array of
666                  * colors and a value in the range [0,1] and interpolates a resulting color in a seemingly predictable manner.
667                  * I haven't looked at it at all.
668                  * @param colors
669                  * @param unit
670                  * @return
671                  */
672                 private int interpColor(int colors[], float unit) {
673                         if (unit <= 0) {
674                                 return colors[0];
675                         }
676                         if (unit >= 1) {
677                                 return colors[colors.length - 1];
678                         }
679
680                         float p = unit * (colors.length - 1);
681                         int i = (int)p;
682                         p -= i;
683
684                         // now p is just the fractional part [0...1) and i is the index
685                         int c0 = colors[i];
686                         int c1 = colors[i+1];
687                         int a = ave(Color.alpha(c0), Color.alpha(c1), p);
688                         int r = ave(Color.red(c0), Color.red(c1), p);
689                         int g = ave(Color.green(c0), Color.green(c1), p);
690                         int b = ave(Color.blue(c0), Color.blue(c1), p);
691
692                         return Color.argb(a, r, g, b);
693                 }
694
695                 /**
696                  * A standard point-in-rect routine.
697                  * @param x
698                  * @param y
699                  * @param r
700                  * @return true if point x,y is in rect r
701                  */
702                 public boolean ptInRect(int x, int y, Rect r) {
703                         return x > r.left && x < r.right && y > r.top && y < r.bottom;
704                 }
705
706                 /**
707                  * Process trackball events.  Used mainly for fine-tuned color adjustment, or alternatively to switch between slider controls.
708                  */
709                 @Override
710                 public boolean dispatchTrackballEvent(MotionEvent event) {
711                         float x = event.getX();
712                         float y = event.getY();
713
714                         //A longer event history implies faster trackball movement.
715                         //Use it to infer a larger jump and therefore faster palette/slider adjustment.
716                         int jump = event.getHistorySize() + 1;
717
718                         switch (event.getAction()) {
719                                 case MotionEvent.ACTION_DOWN: {
720                                         }
721                                         break;
722                                 case MotionEvent.ACTION_MOVE: {
723                                                 //NEW_METHOD_WORK_NEEDED_HERE
724                                                 //To add a new method, replicate and extend the appropriate entry in this list,
725                                                 //depending on whether you use 1D or 2D controllers
726                                                 switch (mMethod) {
727                                                         case METHOD_HS_V_PALETTE:
728                                                                 if (mFocusedControl == 0) {
729                                                                         changeHSPalette(x, y, jump);
730                                                                 }
731                                                                 else if (mFocusedControl == 1) {
732                                                                         if (y < 0)
733                                                                                 changeSlider(mFocusedControl, true, jump);
734                                                                         else if (y > 0)
735                                                                                 changeSlider(mFocusedControl, false, jump);
736                                                                 }
737                                                                 break;
738                                                 }
739                                         }
740                                         break;
741                                 case MotionEvent.ACTION_UP: {
742                                         }
743                                         break;
744                         }
745
746                         return true;
747                 }
748
749                 //NEW_METHOD_WORK_NEEDED_HERE
750                 //To add a new method, replicate and extend the appropriate functions below,
751                 //one per UI controller in the new method
752                 /**
753                  * Effect a trackball change to a 2D palette.
754                  * @param x -1: negative x change, 0: no x change, +1: positive x change.
755                  * @param y -1: negative y change, 0, no y change, +1: positive y change.
756                  * @param jump the amount by which to change.
757                  */
758                 private void changeHSPalette(float x, float y, int jump) {
759                         int x2 = 0, y2 = 0;
760                         if (x < 0)
761                                 x2 = -jump;
762                         else if (x > 0)
763                                 x2 = jump;
764                         if (y < 0)
765                                 y2 = -jump;
766                         else if (y > 0)
767                                 y2 = jump;
768
769                         mCoord[0] += x2;
770                         mCoord[1] += y2;
771
772                         if (mCoord[0] < -PALETTE_RADIUS)
773                                 mCoord[0] = -PALETTE_RADIUS;
774                         else if (mCoord[0] > PALETTE_RADIUS)
775                                 mCoord[0] = PALETTE_RADIUS;
776                         if (mCoord[1] < -PALETTE_RADIUS)
777                                 mCoord[1] = -PALETTE_RADIUS;
778                         else if (mCoord[1] > PALETTE_RADIUS)
779                                 mCoord[1] = PALETTE_RADIUS;
780
781                         float radius = (float)java.lang.Math.sqrt(mCoord[0] * mCoord[0] + mCoord[1] * mCoord[1]);
782                         if (radius > PALETTE_RADIUS)
783                                 radius = PALETTE_RADIUS;
784
785                         float angle = (float)java.lang.Math.atan2(mCoord[1], mCoord[0]);
786                         // need to turn angle [-PI ... PI] into unit [0....1]
787                         float unit = angle/(2*PI);
788                         if (unit < 0) {
789                                 unit += 1;
790                         }
791
792                         mCoord[0] = round(Math.cos(angle) * radius);
793                         mCoord[1] = round(Math.sin(angle) * radius);
794
795                         int c = interpColor(mSpectrumColorsRev, unit);
796                         float[] hsv = new float[3];
797                         Color.colorToHSV(c, hsv);
798                         mHSV[0] = hsv[0];
799                         mHSV[1] = radius / PALETTE_RADIUS;
800                         updateAllFromHSV();
801                         mSwatchNew.setColor(Color.HSVToColor(mHSV));
802
803                         setVerValSlider();
804
805                         invalidate();
806                 }
807
808                 /**
809                  * Effect a trackball change to a 1D slider.
810                  * @param slider id of the slider to be effected
811                  * @param increase true if the change is an increase, false if a decrease
812                  * @param jump the amount by which to change in units of the range [0,255]
813                  */
814                 private void changeSlider(int slider, boolean increase, int jump) {
815                         //NEW_METHOD_WORK_NEEDED_HERE
816                         //It is only necessary to add an entry here for a new method if the new method uses a 1D slider.
817                         //Note, some sliders are horizontal and others are vertical.
818                         //They differ a bit, especially in a sign flip on the vertical axis.
819                         if (mMethod == METHOD_HS_V_PALETTE) {
820                                 //slider *must* equal 1
821
822                                 mHSV[2] += (increase ? jump : -jump) / 256.0f;
823                                 mHSV[2] = pinToUnit(mHSV[2]);
824                                 updateAllFromHSV();
825                                 mCoord[2] = PALETTE_DIM - (int)(mHSV[2] * PALETTE_DIM);
826
827                                 mSwatchNew.setColor(Color.HSVToColor(mHSV));
828
829                                 setOvalValDimmer();
830
831                                 invalidate();
832                         }
833                 }
834
835                 /**
836                  * Keep all colorspace representations in sync.
837                  */
838                 private void updateRGBfromHSV() {
839                         int color = Color.HSVToColor(mHSV);
840                         mRGB[0] = Color.red(color);
841                         mRGB[1] = Color.green(color);
842                         mRGB[2] = Color.blue(color);
843                 }
844
845                 /**
846                  * Keep all colorspace representations in sync.
847                  */
848                 private void updateYUVfromRGB() {
849                         float r = mRGB[0] / 255.0f;
850                         float g = mRGB[1] / 255.0f;
851                         float b = mRGB[2] / 255.0f;
852
853                         ColorMatrix cm = new ColorMatrix();
854                         cm.setRGB2YUV();
855                         final float[] a = cm.getArray();
856
857                         mYUV[0] = a[0] * r + a[1] * g + a[2] * b;
858                         mYUV[0] = pinToUnit(mYUV[0]);
859                         mYUV[1] = a[5] * r + a[6] * g + a[7] * b;
860                         mYUV[1] = pin(mYUV[1], -.5f, .5f);
861                         mYUV[2] = a[10] * r + a[11] * g + a[12] * b;
862                         mYUV[2] = pin(mYUV[2], -.5f, .5f);
863                 }
864
865                 /**
866                  * Keep all colorspace representations in sync.
867                  */
868                 private void updateHexFromHSV() {
869                         //For now, assume 100% opacity
870                         mHexStr = Integer.toHexString(Color.HSVToColor(mHSV)).toUpperCase();
871                         mHexStr = mHexStr.substring(2, mHexStr.length());
872                 }
873
874                 /**
875                  * Keep all colorspace representations in sync.
876                  */
877                 private void updateAllFromHSV() {
878                         //Update mRGB
879                         if (mRGBenabled || mYUVenabled)
880                                 updateRGBfromHSV();
881
882                         //Update mYUV
883                         if (mYUVenabled)
884                                 updateYUVfromRGB();
885
886                         //Update mHexStr
887                         if (mRGBenabled)
888                                 updateHexFromHSV();
889                 }
890
891                 /**
892                  * Process touch events: down, move, and up
893                  */
894                 @Override
895                 public boolean onTouchEvent(MotionEvent event) {
896                         float x = event.getX();
897                         float y = event.getY();
898
899                         //Generate coordinates which are palette=local with the origin at the upper left of the main 2D palette
900                         int y2 = (int)(pin(round(y - PALETTE_POS_Y), PALETTE_DIM));
901
902                         //Generate coordinates which are palette-local with the origin at the center of the main 2D palette
903                         float circlePinnedX = x - PALETTE_POS_X - PALETTE_CENTER_X;
904                         float circlePinnedY = y - PALETTE_POS_Y - PALETTE_CENTER_Y;
905
906                         //Is the event in a swatch?
907                         boolean inSwatchOld = ptInRect(round(x), round(y), mOldSwatchRect);
908                         boolean inSwatchNew = ptInRect(round(x), round(y), mNewSwatchRect);
909
910                         //Get the event's distance from the center of the main 2D palette
911                         float radius = (float)java.lang.Math.sqrt(circlePinnedX * circlePinnedX + circlePinnedY * circlePinnedY);
912
913                         //Is the event in a circle-pinned 2D palette?
914                         boolean inOvalPalette = radius <= PALETTE_RADIUS;
915
916                         //Pin the radius
917                         if (radius > PALETTE_RADIUS)
918                                 radius = PALETTE_RADIUS;
919
920                         //Is the event in a vertical slider to the right of the main 2D palette
921                         boolean inVerSlider = ptInRect(round(x), round(y), mVerSliderRect);
922
923                         switch (event.getAction()) {
924                                 case MotionEvent.ACTION_DOWN:
925                                         mTracking = TRACKED_NONE;
926
927                                         if (inSwatchOld)
928                                                 mTracking = TRACK_SWATCH_OLD;
929                                         else if (inSwatchNew)
930                                                 mTracking = TRACK_SWATCH_NEW;
931
932                                         //NEW_METHOD_WORK_NEEDED_HERE
933                                         //To add a new method, replicate and extend the last entry in this list
934                                         else if (mMethod == METHOD_HS_V_PALETTE) {
935                                                 if (inOvalPalette) {
936                                                         mTracking = TRACK_HS_PALETTE;
937                                                         mFocusedControl = 0;
938                                                 }
939                                                 else if (inVerSlider) {
940                                                         mTracking = TRACK_VER_VALUE_SLIDER;
941                                                         mFocusedControl = 1;
942                                                 }
943                                         }
944                                 case MotionEvent.ACTION_MOVE:
945                                         //NEW_METHOD_WORK_NEEDED_HERE
946                                         //To add a new method, replicate and extend the entries in this list,
947                                         //one per UI controller the new method requires.
948                                         if (mTracking == TRACK_HS_PALETTE) {
949                                                 float angle = (float)java.lang.Math.atan2(circlePinnedY, circlePinnedX);
950                                                 // need to turn angle [-PI ... PI] into unit [0....1]
951                                                 float unit = angle/(2*PI);
952                                                 if (unit < 0) {
953                                                         unit += 1;
954                                                 }
955
956                                                 mCoord[0] = round(Math.cos(angle) * radius);
957                                                 mCoord[1] = round(Math.sin(angle) * radius);
958
959                                                 int c = interpColor(mSpectrumColorsRev, unit);
960                                                 float[] hsv = new float[3];
961                                                 Color.colorToHSV(c, hsv);
962                                                 mHSV[0] = hsv[0];
963                                                 mHSV[1] = radius / PALETTE_RADIUS;
964                                                 updateAllFromHSV();
965                                                 mSwatchNew.setColor(Color.HSVToColor(mHSV));
966
967                                                 setVerValSlider();
968
969                                                 invalidate();
970                                         }
971                                         else if (mTracking == TRACK_VER_VALUE_SLIDER) {
972                                                 if (mCoord[2] != y2) {
973                                                         mCoord[2] = y2;
974                                                         float value = 1.0f - (float)y2 / (float)PALETTE_DIM;
975
976                                                         mHSV[2] = value;
977                                                         updateAllFromHSV();
978                                                         mSwatchNew.setColor(Color.HSVToColor(mHSV));
979
980                                                         setOvalValDimmer();
981
982                                                         invalidate();
983                                                 }
984                                         }
985                                         break;
986                                 case MotionEvent.ACTION_UP:
987                                         //NEW_METHOD_WORK_NEEDED_HERE
988                                         //To add a new method, replicate and extend the last entry in this list.
989                                         if (mTracking == TRACK_SWATCH_OLD && inSwatchOld) {
990                                                 Color.colorToHSV(mOriginalColor, mHSV);
991                                                 mSwatchNew.setColor(mOriginalColor);
992                                                 initUI();
993                                                 invalidate();
994                                         }
995                                         else if (mTracking == TRACK_SWATCH_NEW && inSwatchNew) {
996                                                 mListener.colorChanged(mSwatchNew.getColor());
997                                                 invalidate();
998                                         }
999
1000                                         mTracking= TRACKED_NONE;
1001                                         break;
1002                         }
1003
1004                         return true;
1005                 }
1006         }
1007 }