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