diff src/com/five_ten_sg/connectbot/util/UberColorPickerDialog.java @ 0:0ce5cc452d02

initial version
author Carl Byington <carl@five-ten-sg.com>
date Thu, 22 May 2014 10:41:19 -0700
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/five_ten_sg/connectbot/util/UberColorPickerDialog.java	Thu May 22 10:41:19 2014 -0700
@@ -0,0 +1,947 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * 090408
+ * Keith Wiley
+ * kwiley@keithwiley.com
+ * http://keithwiley.com
+ *
+ * UberColorPickerDialog v1.1
+ *
+ * This color picker was implemented as a (significant) extension of the
+ * ColorPickerDialog class provided in the Android API Demos.  You are free
+ * to drop it unchanged into your own projects or to modify it as you see
+ * fit.  I would appreciate it if this comment block were let intact,
+ * merely for credit's sake.
+ *
+ * Enjoy!
+ */
+
+package com.five_ten_sg.connectbot.util;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorMatrix;
+import android.graphics.ComposeShader;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.RadialGradient;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Shader;
+import android.graphics.SweepGradient;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.GradientDrawable.Orientation;
+import android.os.Bundle;
+import android.util.DisplayMetrics;
+import android.util.FloatMath;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * UberColorPickerDialog is a seriously enhanced version of the UberColorPickerDialog
+ * class provided in the Android API Demos.<p>
+ *
+ * NOTE (from Kenny Root): This is a VERY slimmed down version custom for ConnectBot.
+ * Visit Keith's site for the full version at the URL listed in the author line.<p>
+ *
+ * @author Keith Wiley, kwiley@keithwiley.com, http://keithwiley.com
+ */
+public class UberColorPickerDialog extends Dialog {
+    private final OnColorChangedListener mListener;
+    private final int mInitialColor;
+
+    /**
+     * Callback to the creator of the dialog, informing the creator of a new color and notifying that the dialog is about to dismiss.
+     */
+    public interface OnColorChangedListener {
+        void colorChanged(int color);
+    }
+
+    /**
+     * Ctor
+     * @param context
+     * @param listener
+     * @param initialColor
+     * @param showTitle If true, a title is shown across the top of the dialog.  If false a toast is shown instead.
+     */
+    public UberColorPickerDialog(Context context,
+                                 OnColorChangedListener listener,
+                                 int initialColor) {
+        super(context);
+        mListener = listener;
+        mInitialColor = initialColor;
+    }
+
+    /**
+     * Activity entry point
+     */
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        OnColorChangedListener l = new OnColorChangedListener() {
+            public void colorChanged(int color) {
+                mListener.colorChanged(color);
+                dismiss();
+            }
+        };
+        DisplayMetrics dm = new DisplayMetrics();
+        getWindow().getWindowManager().getDefaultDisplay().getMetrics(dm);
+        int screenWidth = dm.widthPixels;
+        int screenHeight = dm.heightPixels;
+        setTitle("Pick a color (try the trackball)");
+
+        try {
+            setContentView(new ColorPickerView(getContext(), l, screenWidth, screenHeight, mInitialColor));
+        }
+        catch (Exception e) {
+            //There is currently only one kind of ctor exception, that where no methods are enabled.
+            dismiss();  //This doesn't work!  The dialog is still shown (its title at least, the layout is empty from the exception being thrown).  <sigh>
+        }
+    }
+
+    /**
+     * ColorPickerView is the meat of this color picker (as opposed to the enclosing class).
+     * All the heavy lifting is done directly by this View subclass.
+     * <P>
+     * You can enable/disable whichever color chooser methods you want by modifying the ENABLED_METHODS switches.  They *should*
+     * do all the work required to properly enable/disable methods without losing track of what goes with what and what maps to what.
+     * <P>
+     * If you add a new color chooser method, do a text search for "NEW_METHOD_WORK_NEEDED_HERE".  That tag indicates all
+     * the locations in the code that will have to be amended in order to properly add a new color chooser method.
+     * 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.
+     */
+    private static class ColorPickerView extends View {
+        private static int SWATCH_WIDTH = 95;
+        private static final int SWATCH_HEIGHT = 60;
+
+        private static int PALETTE_POS_X = 0;
+        private static int PALETTE_POS_Y = SWATCH_HEIGHT;
+        private static final int PALETTE_DIM = SWATCH_WIDTH * 2;
+        private static final int PALETTE_RADIUS = PALETTE_DIM / 2;
+        private static final int PALETTE_CENTER_X = PALETTE_RADIUS;
+        private static final int PALETTE_CENTER_Y = PALETTE_RADIUS;
+
+        private static final int SLIDER_THICKNESS = 40;
+
+        private static int VIEW_DIM_X = PALETTE_DIM;
+        private static int VIEW_DIM_Y = SWATCH_HEIGHT;
+
+        //NEW_METHOD_WORK_NEEDED_HERE
+        private static final int METHOD_HS_V_PALETTE = 0;
+
+        //NEW_METHOD_WORK_NEEDED_HERE
+        //Add a new entry to the list for each controller in the new method
+        private static final int TRACKED_NONE = -1; //No object on screen is currently being tracked
+        private static final int TRACK_SWATCH_OLD = 10;
+        private static final int TRACK_SWATCH_NEW = 11;
+        private static final int TRACK_HS_PALETTE = 30;
+        private static final int TRACK_VER_VALUE_SLIDER = 31;
+
+        private static final int TEXT_SIZE = 12;
+        private static int[] TEXT_HSV_POS = new int[2];
+        private static int[] TEXT_RGB_POS = new int[2];
+        private static int[] TEXT_YUV_POS = new int[2];
+        private static int[] TEXT_HEX_POS = new int[2];
+
+        private static final float PI = 3.141592653589793f;
+
+        private int mMethod = METHOD_HS_V_PALETTE;
+        private int mTracking = TRACKED_NONE;   //What object on screen is currently being tracked for movement
+
+        //Zillions of persistant Paint objecs for drawing the View
+
+        private Paint mSwatchOld, mSwatchNew;
+
+        //NEW_METHOD_WORK_NEEDED_HERE
+        //Add Paints to represent the palettes of the new method's UI controllers
+        private Paint mOvalHueSat;
+
+        private Bitmap mVerSliderBM;
+        private Canvas mVerSliderCv;
+
+        private Bitmap[] mHorSlidersBM = new Bitmap[3];
+        private Canvas[] mHorSlidersCv = new Canvas[3];
+
+        private Paint mValDimmer;
+
+        //NEW_METHOD_WORK_NEEDED_HERE
+        //Add Paints to represent the icon for the new method
+        private Paint mOvalHueSatSmall;
+
+        private Paint mPosMarker;
+        private Paint mText;
+
+        private Rect mOldSwatchRect = new Rect();
+        private Rect mNewSwatchRect = new Rect();
+        private Rect mPaletteRect = new Rect();
+        private Rect mVerSliderRect = new Rect();
+
+        private int[] mSpectrumColorsRev;
+        private int mOriginalColor = 0; //The color passed in at the beginning, which can be reverted to at any time by tapping the old swatch.
+        private float[] mHSV = new float[3];
+        private int[] mRGB = new int[3];
+        private float[] mYUV = new float[3];
+        private String mHexStr = "";
+        private boolean mHSVenabled = true; //Only true if an HSV method is enabled
+        private boolean mRGBenabled = true; //Only true if an RGB method is enabled
+        private boolean mYUVenabled = true; //Only true if a YUV method is enabled
+        private boolean mHexenabled = true; //Only true if an RGB method is enabled
+        private int[] mCoord = new int[3];      //For drawing slider/palette markers
+        private int mFocusedControl = -1;   //Which control receives trackball events.
+        private OnColorChangedListener mListener;
+
+        /**
+         * Ctor.
+         * @param c
+         * @param l
+         * @param width Used to determine orientation and adjust layout accordingly
+         * @param height Used to determine orientation and adjust layout accordingly
+         * @param color The initial color
+         * @throws Exception
+         */
+        ColorPickerView(Context c, OnColorChangedListener l, int width, int height, int color)
+        throws Exception {
+            super(c);
+            //We need to make the dialog focusable to retrieve trackball events.
+            setFocusable(true);
+            mListener = l;
+            mOriginalColor = color;
+            Color.colorToHSV(color, mHSV);
+            updateAllFromHSV();
+
+            //Setup the layout based on whether this is a portrait or landscape orientation.
+            if (width <= height) {  //Portrait layout
+                SWATCH_WIDTH = (PALETTE_DIM + SLIDER_THICKNESS) / 2;
+                PALETTE_POS_X = 0;
+                PALETTE_POS_Y = TEXT_SIZE * 4 + SWATCH_HEIGHT;
+                //Set more rects, lots of rects
+                mOldSwatchRect.set(0, TEXT_SIZE * 4, SWATCH_WIDTH, TEXT_SIZE * 4 + SWATCH_HEIGHT);
+                mNewSwatchRect.set(SWATCH_WIDTH, TEXT_SIZE * 4, SWATCH_WIDTH * 2, TEXT_SIZE * 4 + SWATCH_HEIGHT);
+                mPaletteRect.set(0, PALETTE_POS_Y, PALETTE_DIM, PALETTE_POS_Y + PALETTE_DIM);
+                mVerSliderRect.set(PALETTE_DIM, PALETTE_POS_Y, PALETTE_DIM + SLIDER_THICKNESS, PALETTE_POS_Y + PALETTE_DIM);
+                TEXT_HSV_POS[0] = 3;
+                TEXT_HSV_POS[1] = 0;
+                TEXT_RGB_POS[0] = TEXT_HSV_POS[0] + 50;
+                TEXT_RGB_POS[1] = TEXT_HSV_POS[1];
+                TEXT_YUV_POS[0] = TEXT_HSV_POS[0] + 100;
+                TEXT_YUV_POS[1] = TEXT_HSV_POS[1];
+                TEXT_HEX_POS[0] = TEXT_HSV_POS[0] + 150;
+                TEXT_HEX_POS[1] = TEXT_HSV_POS[1];
+                VIEW_DIM_X = PALETTE_DIM + SLIDER_THICKNESS;
+                VIEW_DIM_Y = SWATCH_HEIGHT + PALETTE_DIM + TEXT_SIZE * 4;
+            }
+            else {  //Landscape layout
+                SWATCH_WIDTH = 110;
+                PALETTE_POS_X = SWATCH_WIDTH;
+                PALETTE_POS_Y = 0;
+                //Set more rects, lots of rects
+                mOldSwatchRect.set(0, TEXT_SIZE * 7, SWATCH_WIDTH, TEXT_SIZE * 7 + SWATCH_HEIGHT);
+                mNewSwatchRect.set(0, TEXT_SIZE * 7 + SWATCH_HEIGHT, SWATCH_WIDTH, TEXT_SIZE * 7 + SWATCH_HEIGHT * 2);
+                mPaletteRect.set(SWATCH_WIDTH, PALETTE_POS_Y, SWATCH_WIDTH + PALETTE_DIM, PALETTE_POS_Y + PALETTE_DIM);
+                mVerSliderRect.set(SWATCH_WIDTH + PALETTE_DIM, PALETTE_POS_Y, SWATCH_WIDTH + PALETTE_DIM + SLIDER_THICKNESS, PALETTE_POS_Y + PALETTE_DIM);
+                TEXT_HSV_POS[0] = 3;
+                TEXT_HSV_POS[1] = 0;
+                TEXT_RGB_POS[0] = TEXT_HSV_POS[0];
+                TEXT_RGB_POS[1] = (int)(TEXT_HSV_POS[1] + TEXT_SIZE * 3.5);
+                TEXT_YUV_POS[0] = TEXT_HSV_POS[0] + 50;
+                TEXT_YUV_POS[1] = (int)(TEXT_HSV_POS[1] + TEXT_SIZE * 3.5);
+                TEXT_HEX_POS[0] = TEXT_HSV_POS[0] + 50;
+                TEXT_HEX_POS[1] = TEXT_HSV_POS[1];
+                VIEW_DIM_X = PALETTE_POS_X + PALETTE_DIM + SLIDER_THICKNESS;
+                VIEW_DIM_Y = Math.max(mNewSwatchRect.bottom, PALETTE_DIM);
+            }
+
+            //Rainbows make everybody happy!
+            mSpectrumColorsRev = new int[] {
+                0xFFFF0000, 0xFFFF00FF, 0xFF0000FF, 0xFF00FFFF,
+                0xFF00FF00, 0xFFFFFF00, 0xFFFF0000,
+            };
+            //Setup all the Paint and Shader objects.  There are lots of them!
+            //NEW_METHOD_WORK_NEEDED_HERE
+            //Add Paints to represent the palettes of the new method's UI controllers
+            mSwatchOld = new Paint(Paint.ANTI_ALIAS_FLAG);
+            mSwatchOld.setStyle(Paint.Style.FILL);
+            mSwatchOld.setColor(Color.HSVToColor(mHSV));
+            mSwatchNew = new Paint(Paint.ANTI_ALIAS_FLAG);
+            mSwatchNew.setStyle(Paint.Style.FILL);
+            mSwatchNew.setColor(Color.HSVToColor(mHSV));
+            Shader shaderA = new SweepGradient(0, 0, mSpectrumColorsRev, null);
+            Shader shaderB = new RadialGradient(0, 0, PALETTE_CENTER_X, 0xFFFFFFFF, 0xFF000000, Shader.TileMode.CLAMP);
+            Shader shader = new ComposeShader(shaderA, shaderB, PorterDuff.Mode.SCREEN);
+            mOvalHueSat = new Paint(Paint.ANTI_ALIAS_FLAG);
+            mOvalHueSat.setShader(shader);
+            mOvalHueSat.setStyle(Paint.Style.FILL);
+            mOvalHueSat.setDither(true);
+            mVerSliderBM = Bitmap.createBitmap(SLIDER_THICKNESS, PALETTE_DIM, Bitmap.Config.RGB_565);
+            mVerSliderCv = new Canvas(mVerSliderBM);
+
+            for (int i = 0; i < 3; i++) {
+                mHorSlidersBM[i] = Bitmap.createBitmap(PALETTE_DIM, SLIDER_THICKNESS, Bitmap.Config.RGB_565);
+                mHorSlidersCv[i] = new Canvas(mHorSlidersBM[i]);
+            }
+
+            mValDimmer = new Paint(Paint.ANTI_ALIAS_FLAG);
+            mValDimmer.setStyle(Paint.Style.FILL);
+            mValDimmer.setDither(true);
+            mValDimmer.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY));
+            //Whew, we're done making the big Paints and Shaders for the swatches, palettes, and sliders.
+            //Now we need to make the Paints and Shaders that will draw the little method icons in the method selector list.
+            //NEW_METHOD_WORK_NEEDED_HERE
+            //Add Paints to represent the icon for the new method
+            shaderA = new SweepGradient(0, 0, mSpectrumColorsRev, null);
+            shaderB = new RadialGradient(0, 0, PALETTE_DIM / 2, 0xFFFFFFFF, 0xFF000000, Shader.TileMode.CLAMP);
+            shader = new ComposeShader(shaderA, shaderB, PorterDuff.Mode.SCREEN);
+            mOvalHueSatSmall = new Paint(Paint.ANTI_ALIAS_FLAG);
+            mOvalHueSatSmall.setShader(shader);
+            mOvalHueSatSmall.setStyle(Paint.Style.FILL);
+            //Make a simple stroking Paint for drawing markers and borders and stuff like that.
+            mPosMarker = new Paint(Paint.ANTI_ALIAS_FLAG);
+            mPosMarker.setStyle(Paint.Style.STROKE);
+            mPosMarker.setStrokeWidth(2);
+            //Make a basic text Paint.
+            mText = new Paint(Paint.ANTI_ALIAS_FLAG);
+            mText.setTextSize(TEXT_SIZE);
+            mText.setColor(Color.WHITE);
+            //Kickstart
+            initUI();
+        }
+
+        /**
+         * Draw the entire view (the entire dialog).
+         */
+        @Override
+        protected void onDraw(Canvas canvas) {
+            //Draw the old and new swatches
+            drawSwatches(canvas);
+            //Write the text
+            writeColorParams(canvas);
+
+            //Draw the palette and sliders (the UI)
+            if (mMethod == METHOD_HS_V_PALETTE)
+                drawHSV1Palette(canvas);
+        }
+
+        /**
+         * Draw the old and new swatches.
+         * @param canvas
+         */
+        private void drawSwatches(Canvas canvas) {
+            float[] hsv = new float[3];
+            mText.setTextSize(16);
+            //Draw the original swatch
+            canvas.drawRect(mOldSwatchRect, mSwatchOld);
+            Color.colorToHSV(mOriginalColor, hsv);
+
+            //if (UberColorPickerDialog.isGray(mColor)) //Don't need this right here, but imp't to note
+            //  hsv[1] = 0;
+            if (hsv[2] > .5)
+                mText.setColor(Color.BLACK);
+
+            canvas.drawText("Revert", mOldSwatchRect.left + SWATCH_WIDTH / 2 - mText.measureText("Revert") / 2, mOldSwatchRect.top + 16, mText);
+            mText.setColor(Color.WHITE);
+            //Draw the new swatch
+            canvas.drawRect(mNewSwatchRect, mSwatchNew);
+
+            if (mHSV[2] > .5)
+                mText.setColor(Color.BLACK);
+
+            canvas.drawText("Accept", mNewSwatchRect.left + SWATCH_WIDTH / 2 - mText.measureText("Accept") / 2, mNewSwatchRect.top + 16, mText);
+            mText.setColor(Color.WHITE);
+            mText.setTextSize(TEXT_SIZE);
+        }
+
+        /**
+         * Write the color parametes (HSV, RGB, YUV, Hex, etc.).
+         * @param canvas
+         */
+        private void writeColorParams(Canvas canvas) {
+            if (mHSVenabled) {
+                canvas.drawText("H: " + Integer.toString((int)(mHSV[0] / 360.0f * 255)), TEXT_HSV_POS[0], TEXT_HSV_POS[1] + TEXT_SIZE, mText);
+                canvas.drawText("S: " + Integer.toString((int)(mHSV[1] * 255)), TEXT_HSV_POS[0], TEXT_HSV_POS[1] + TEXT_SIZE * 2, mText);
+                canvas.drawText("V: " + Integer.toString((int)(mHSV[2] * 255)), TEXT_HSV_POS[0], TEXT_HSV_POS[1] + TEXT_SIZE * 3, mText);
+            }
+
+            if (mRGBenabled) {
+                canvas.drawText("R: " + mRGB[0], TEXT_RGB_POS[0], TEXT_RGB_POS[1] + TEXT_SIZE, mText);
+                canvas.drawText("G: " + mRGB[1], TEXT_RGB_POS[0], TEXT_RGB_POS[1] + TEXT_SIZE * 2, mText);
+                canvas.drawText("B: " + mRGB[2], TEXT_RGB_POS[0], TEXT_RGB_POS[1] + TEXT_SIZE * 3, mText);
+            }
+
+            if (mYUVenabled) {
+                canvas.drawText("Y: " + Integer.toString((int)(mYUV[0] * 255)), TEXT_YUV_POS[0], TEXT_YUV_POS[1] + TEXT_SIZE, mText);
+                canvas.drawText("U: " + Integer.toString((int)((mYUV[1] + .5f) * 255)), TEXT_YUV_POS[0], TEXT_YUV_POS[1] + TEXT_SIZE * 2, mText);
+                canvas.drawText("V: " + Integer.toString((int)((mYUV[2] + .5f) * 255)), TEXT_YUV_POS[0], TEXT_YUV_POS[1] + TEXT_SIZE * 3, mText);
+            }
+
+            if (mHexenabled)
+                canvas.drawText("#" + mHexStr, TEXT_HEX_POS[0], TEXT_HEX_POS[1] + TEXT_SIZE, mText);
+        }
+
+        /**
+         * Place a small circle on the 2D palette to indicate the current values.
+         * @param canvas
+         * @param markerPosX
+         * @param markerPosY
+         */
+        private void mark2DPalette(Canvas canvas, int markerPosX, int markerPosY) {
+            mPosMarker.setColor(Color.BLACK);
+            canvas.drawOval(new RectF(markerPosX - 5, markerPosY - 5, markerPosX + 5, markerPosY + 5), mPosMarker);
+            mPosMarker.setColor(Color.WHITE);
+            canvas.drawOval(new RectF(markerPosX - 3, markerPosY - 3, markerPosX + 3, markerPosY + 3), mPosMarker);
+        }
+
+        /**
+         * Draw a line across the slider to indicate its current value.
+         * @param canvas
+         * @param markerPos
+         */
+        private void markVerSlider(Canvas canvas, int markerPos) {
+            mPosMarker.setColor(Color.BLACK);
+            canvas.drawRect(new Rect(0, markerPos - 2, SLIDER_THICKNESS, markerPos + 3), mPosMarker);
+            mPosMarker.setColor(Color.WHITE);
+            canvas.drawRect(new Rect(0, markerPos, SLIDER_THICKNESS, markerPos + 1), mPosMarker);
+        }
+
+        /**
+         * Frame the slider to indicate that it has trackball focus.
+         * @param canvas
+         */
+        private void hilightFocusedVerSlider(Canvas canvas) {
+            mPosMarker.setColor(Color.WHITE);
+            canvas.drawRect(new Rect(0, 0, SLIDER_THICKNESS, PALETTE_DIM), mPosMarker);
+            mPosMarker.setColor(Color.BLACK);
+            canvas.drawRect(new Rect(2, 2, SLIDER_THICKNESS - 2, PALETTE_DIM - 2), mPosMarker);
+        }
+
+        /**
+         * Frame the 2D palette to indicate that it has trackball focus.
+         * @param canvas
+         */
+        private void hilightFocusedOvalPalette(Canvas canvas) {
+            mPosMarker.setColor(Color.WHITE);
+            canvas.drawOval(new RectF(-PALETTE_RADIUS, -PALETTE_RADIUS, PALETTE_RADIUS, PALETTE_RADIUS), mPosMarker);
+            mPosMarker.setColor(Color.BLACK);
+            canvas.drawOval(new RectF(-PALETTE_RADIUS + 2, -PALETTE_RADIUS + 2, PALETTE_RADIUS - 2, PALETTE_RADIUS - 2), mPosMarker);
+        }
+
+        //NEW_METHOD_WORK_NEEDED_HERE
+        //To add a new method, replicate the basic draw functions here.  Use the 2D palette or 1D sliders as templates for the new method.
+        /**
+         * Draw the UI for HSV with angular H and radial S combined in 2D and a 1D V slider.
+         * @param canvas
+         */
+        private void drawHSV1Palette(Canvas canvas) {
+            canvas.save();
+            canvas.translate(PALETTE_POS_X, PALETTE_POS_Y);
+            //Draw the 2D palette
+            canvas.translate(PALETTE_CENTER_X, PALETTE_CENTER_Y);
+            canvas.drawOval(new RectF(-PALETTE_RADIUS, -PALETTE_RADIUS, PALETTE_RADIUS, PALETTE_RADIUS), mOvalHueSat);
+            canvas.drawOval(new RectF(-PALETTE_RADIUS, -PALETTE_RADIUS, PALETTE_RADIUS, PALETTE_RADIUS), mValDimmer);
+
+            if (mFocusedControl == 0)
+                hilightFocusedOvalPalette(canvas);
+
+            mark2DPalette(canvas, mCoord[0], mCoord[1]);
+            canvas.translate(-PALETTE_CENTER_X, -PALETTE_CENTER_Y);
+            //Draw the 1D slider
+            canvas.translate(PALETTE_DIM, 0);
+            canvas.drawBitmap(mVerSliderBM, 0, 0, null);
+
+            if (mFocusedControl == 1)
+                hilightFocusedVerSlider(canvas);
+
+            markVerSlider(canvas, mCoord[2]);
+            canvas.restore();
+        }
+
+        /**
+         * Initialize the current color chooser's UI (set its color parameters and set its palette and slider values accordingly).
+         */
+        private void initUI() {
+            initHSV1Palette();
+            //Focus on the first controller (arbitrary).
+            mFocusedControl = 0;
+        }
+
+        //NEW_METHOD_WORK_NEEDED_HERE
+        //To add a new method, replicate and extend the last init function shown below
+        /**
+         * Initialize a color chooser.
+         */
+        private void initHSV1Palette() {
+            setOvalValDimmer();
+            setVerValSlider();
+            float angle = 2 * PI - mHSV[0] / (180 / 3.1415927f);
+            float radius = mHSV[1] * PALETTE_RADIUS;
+            mCoord[0] = (int)(FloatMath.cos(angle) * radius);
+            mCoord[1] = (int)(FloatMath.sin(angle) * radius);
+            mCoord[2] = PALETTE_DIM - (int)(mHSV[2] * PALETTE_DIM);
+        }
+
+        //NEW_METHOD_WORK_NEEDED_HERE
+        //To add a new method, replicate and extend the set functions below, one per UI controller in the new method
+        /**
+         * Adjust a Paint which, when painted, dims its underlying object to show the effects of varying value (brightness).
+         */
+        private void setOvalValDimmer() {
+            float[] hsv = new float[3];
+            hsv[0] = mHSV[0];
+            hsv[1] = 0;
+            hsv[2] = mHSV[2];
+            int gray = Color.HSVToColor(hsv);
+            mValDimmer.setColor(gray);
+        }
+
+        /**
+         * Create a linear gradient shader to show variations in value.
+         */
+        private void setVerValSlider() {
+            float[] hsv = new float[3];
+            hsv[0] = mHSV[0];
+            hsv[1] = mHSV[1];
+            hsv[2] = 1;
+            int col = Color.HSVToColor(hsv);
+            int colors[] = new int[2];
+            colors[0] = col;
+            colors[1] = 0xFF000000;
+            GradientDrawable gradDraw = new GradientDrawable(Orientation.TOP_BOTTOM, colors);
+            gradDraw.setDither(true);
+            gradDraw.setLevel(10000);
+            gradDraw.setBounds(0, 0, SLIDER_THICKNESS, PALETTE_DIM);
+            gradDraw.draw(mVerSliderCv);
+        }
+
+        /**
+         * Report the correct tightly bounded dimensions of the view.
+         */
+        @Override
+        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+            setMeasuredDimension(VIEW_DIM_X, VIEW_DIM_Y);
+        }
+
+        /**
+         * Wrap Math.round().  I'm not a Java expert.  Is this the only way to avoid writing "(int)Math.round" everywhere?
+         * @param x
+         * @return
+         */
+        private int round(double x) {
+            return (int)Math.round(x);
+        }
+
+        /**
+         * Limit a value to the range [0,1].
+         * @param n
+         * @return
+         */
+        private float pinToUnit(float n) {
+            if (n < 0) {
+                n = 0;
+            }
+            else if (n > 1) {
+                n = 1;
+            }
+
+            return n;
+        }
+
+        /**
+         * Limit a value to the range [0,max].
+         * @param n
+         * @param max
+         * @return
+         */
+        private float pin(float n, float max) {
+            if (n < 0) {
+                n = 0;
+            }
+            else if (n > max) {
+                n = max;
+            }
+
+            return n;
+        }
+
+        /**
+         * Limit a value to the range [min,max].
+         * @param n
+         * @param min
+         * @param max
+         * @return
+         */
+        private float pin(float n, float min, float max) {
+            if (n < min) {
+                n = min;
+            }
+            else if (n > max) {
+                n = max;
+            }
+
+            return n;
+        }
+
+        /**
+         * No clue what this does (some sort of average/mean I presume).  It came with the original UberColorPickerDialog
+         * 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.
+         * @param s
+         * @param d
+         * @param p
+         * @return
+         */
+        private int ave(int s, int d, float p) {
+            return s + round(p * (d - s));
+        }
+
+        /**
+         * Came with the original UberColorPickerDialog in the API Demos, wasn't documented.  I believe it takes an array of
+         * colors and a value in the range [0,1] and interpolates a resulting color in a seemingly predictable manner.
+         * I haven't looked at it at all.
+         * @param colors
+         * @param unit
+         * @return
+         */
+        private int interpColor(int colors[], float unit) {
+            if (unit <= 0) {
+                return colors[0];
+            }
+
+            if (unit >= 1) {
+                return colors[colors.length - 1];
+            }
+
+            float p = unit * (colors.length - 1);
+            int i = (int)p;
+            p -= i;
+            // now p is just the fractional part [0...1) and i is the index
+            int c0 = colors[i];
+            int c1 = colors[i + 1];
+            int a = ave(Color.alpha(c0), Color.alpha(c1), p);
+            int r = ave(Color.red(c0), Color.red(c1), p);
+            int g = ave(Color.green(c0), Color.green(c1), p);
+            int b = ave(Color.blue(c0), Color.blue(c1), p);
+            return Color.argb(a, r, g, b);
+        }
+
+        /**
+         * A standard point-in-rect routine.
+         * @param x
+         * @param y
+         * @param r
+         * @return true if point x,y is in rect r
+         */
+        public boolean ptInRect(int x, int y, Rect r) {
+            return x > r.left && x < r.right && y > r.top && y < r.bottom;
+        }
+
+        /**
+         * Process trackball events.  Used mainly for fine-tuned color adjustment, or alternatively to switch between slider controls.
+         */
+        @Override
+        public boolean dispatchTrackballEvent(MotionEvent event) {
+            float x = event.getX();
+            float y = event.getY();
+            //A longer event history implies faster trackball movement.
+            //Use it to infer a larger jump and therefore faster palette/slider adjustment.
+            int jump = event.getHistorySize() + 1;
+
+            switch (event.getAction()) {
+                case MotionEvent.ACTION_DOWN: {
+                    }
+                    break;
+
+                case MotionEvent.ACTION_MOVE: {
+                        //NEW_METHOD_WORK_NEEDED_HERE
+                        //To add a new method, replicate and extend the appropriate entry in this list,
+                        //depending on whether you use 1D or 2D controllers
+                        switch (mMethod) {
+                            case METHOD_HS_V_PALETTE:
+                                if (mFocusedControl == 0) {
+                                    changeHSPalette(x, y, jump);
+                                }
+                                else if (mFocusedControl == 1) {
+                                    if (y < 0)
+                                        changeSlider(mFocusedControl, true, jump);
+                                    else if (y > 0)
+                                        changeSlider(mFocusedControl, false, jump);
+                                }
+
+                                break;
+                        }
+                    }
+                    break;
+
+                case MotionEvent.ACTION_UP: {
+                    }
+                    break;
+            }
+
+            return true;
+        }
+
+        //NEW_METHOD_WORK_NEEDED_HERE
+        //To add a new method, replicate and extend the appropriate functions below,
+        //one per UI controller in the new method
+        /**
+         * Effect a trackball change to a 2D palette.
+         * @param x -1: negative x change, 0: no x change, +1: positive x change.
+         * @param y -1: negative y change, 0, no y change, +1: positive y change.
+         * @param jump the amount by which to change.
+         */
+        private void changeHSPalette(float x, float y, int jump) {
+            int x2 = 0, y2 = 0;
+
+            if (x < 0)
+                x2 = -jump;
+            else if (x > 0)
+                x2 = jump;
+
+            if (y < 0)
+                y2 = -jump;
+            else if (y > 0)
+                y2 = jump;
+
+            mCoord[0] += x2;
+            mCoord[1] += y2;
+
+            if (mCoord[0] < -PALETTE_RADIUS)
+                mCoord[0] = -PALETTE_RADIUS;
+            else if (mCoord[0] > PALETTE_RADIUS)
+                mCoord[0] = PALETTE_RADIUS;
+
+            if (mCoord[1] < -PALETTE_RADIUS)
+                mCoord[1] = -PALETTE_RADIUS;
+            else if (mCoord[1] > PALETTE_RADIUS)
+                mCoord[1] = PALETTE_RADIUS;
+
+            float radius = FloatMath.sqrt(mCoord[0] * mCoord[0] + mCoord[1] * mCoord[1]);
+
+            if (radius > PALETTE_RADIUS)
+                radius = PALETTE_RADIUS;
+
+            float angle = (float)Math.atan2(mCoord[1], mCoord[0]);
+            // need to turn angle [-PI ... PI] into unit [0....1]
+            float unit = angle / (2 * PI);
+
+            if (unit < 0) {
+                unit += 1;
+            }
+
+            mCoord[0] = round(FloatMath.cos(angle) * radius);
+            mCoord[1] = round(FloatMath.sin(angle) * radius);
+            int c = interpColor(mSpectrumColorsRev, unit);
+            float[] hsv = new float[3];
+            Color.colorToHSV(c, hsv);
+            mHSV[0] = hsv[0];
+            mHSV[1] = radius / PALETTE_RADIUS;
+            updateAllFromHSV();
+            mSwatchNew.setColor(Color.HSVToColor(mHSV));
+            setVerValSlider();
+            invalidate();
+        }
+
+        /**
+         * Effect a trackball change to a 1D slider.
+         * @param slider id of the slider to be effected
+         * @param increase true if the change is an increase, false if a decrease
+         * @param jump the amount by which to change in units of the range [0,255]
+         */
+        private void changeSlider(int slider, boolean increase, int jump) {
+            //NEW_METHOD_WORK_NEEDED_HERE
+            //It is only necessary to add an entry here for a new method if the new method uses a 1D slider.
+            //Note, some sliders are horizontal and others are vertical.
+            //They differ a bit, especially in a sign flip on the vertical axis.
+            if (mMethod == METHOD_HS_V_PALETTE) {
+                //slider *must* equal 1
+                mHSV[2] += (increase ? jump : -jump) / 256.0f;
+                mHSV[2] = pinToUnit(mHSV[2]);
+                updateAllFromHSV();
+                mCoord[2] = PALETTE_DIM - (int)(mHSV[2] * PALETTE_DIM);
+                mSwatchNew.setColor(Color.HSVToColor(mHSV));
+                setOvalValDimmer();
+                invalidate();
+            }
+        }
+
+        /**
+         * Keep all colorspace representations in sync.
+         */
+        private void updateRGBfromHSV() {
+            int color = Color.HSVToColor(mHSV);
+            mRGB[0] = Color.red(color);
+            mRGB[1] = Color.green(color);
+            mRGB[2] = Color.blue(color);
+        }
+
+        /**
+         * Keep all colorspace representations in sync.
+         */
+        private void updateYUVfromRGB() {
+            float r = mRGB[0] / 255.0f;
+            float g = mRGB[1] / 255.0f;
+            float b = mRGB[2] / 255.0f;
+            ColorMatrix cm = new ColorMatrix();
+            cm.setRGB2YUV();
+            final float[] a = cm.getArray();
+            mYUV[0] = a[0] * r + a[1] * g + a[2] * b;
+            mYUV[0] = pinToUnit(mYUV[0]);
+            mYUV[1] = a[5] * r + a[6] * g + a[7] * b;
+            mYUV[1] = pin(mYUV[1], -.5f, .5f);
+            mYUV[2] = a[10] * r + a[11] * g + a[12] * b;
+            mYUV[2] = pin(mYUV[2], -.5f, .5f);
+        }
+
+        /**
+         * Keep all colorspace representations in sync.
+         */
+        private void updateHexFromHSV() {
+            //For now, assume 100% opacity
+            mHexStr = Integer.toHexString(Color.HSVToColor(mHSV)).toUpperCase();
+            mHexStr = mHexStr.substring(2, mHexStr.length());
+        }
+
+        /**
+         * Keep all colorspace representations in sync.
+         */
+        private void updateAllFromHSV() {
+            //Update mRGB
+            if (mRGBenabled || mYUVenabled)
+                updateRGBfromHSV();
+
+            //Update mYUV
+            if (mYUVenabled)
+                updateYUVfromRGB();
+
+            //Update mHexStr
+            if (mRGBenabled)
+                updateHexFromHSV();
+        }
+
+        /**
+         * Process touch events: down, move, and up
+         */
+        @Override
+        public boolean onTouchEvent(MotionEvent event) {
+            float x = event.getX();
+            float y = event.getY();
+            //Generate coordinates which are palette=local with the origin at the upper left of the main 2D palette
+            int y2 = (int)(pin(round(y - PALETTE_POS_Y), PALETTE_DIM));
+            //Generate coordinates which are palette-local with the origin at the center of the main 2D palette
+            float circlePinnedX = x - PALETTE_POS_X - PALETTE_CENTER_X;
+            float circlePinnedY = y - PALETTE_POS_Y - PALETTE_CENTER_Y;
+            //Is the event in a swatch?
+            boolean inSwatchOld = ptInRect(round(x), round(y), mOldSwatchRect);
+            boolean inSwatchNew = ptInRect(round(x), round(y), mNewSwatchRect);
+            //Get the event's distance from the center of the main 2D palette
+            float radius = FloatMath.sqrt(circlePinnedX * circlePinnedX + circlePinnedY * circlePinnedY);
+            //Is the event in a circle-pinned 2D palette?
+            boolean inOvalPalette = radius <= PALETTE_RADIUS;
+
+            //Pin the radius
+            if (radius > PALETTE_RADIUS)
+                radius = PALETTE_RADIUS;
+
+            //Is the event in a vertical slider to the right of the main 2D palette
+            boolean inVerSlider = ptInRect(round(x), round(y), mVerSliderRect);
+
+            switch (event.getAction()) {
+                case MotionEvent.ACTION_DOWN:
+                    mTracking = TRACKED_NONE;
+
+                    if (inSwatchOld)
+                        mTracking = TRACK_SWATCH_OLD;
+                    else if (inSwatchNew)
+                        mTracking = TRACK_SWATCH_NEW;
+                    //NEW_METHOD_WORK_NEEDED_HERE
+                    //To add a new method, replicate and extend the last entry in this list
+                    else if (mMethod == METHOD_HS_V_PALETTE) {
+                        if (inOvalPalette) {
+                            mTracking = TRACK_HS_PALETTE;
+                            mFocusedControl = 0;
+                        }
+                        else if (inVerSlider) {
+                            mTracking = TRACK_VER_VALUE_SLIDER;
+                            mFocusedControl = 1;
+                        }
+                    }
+
+                case MotionEvent.ACTION_MOVE:
+
+                    //NEW_METHOD_WORK_NEEDED_HERE
+                    //To add a new method, replicate and extend the entries in this list,
+                    //one per UI controller the new method requires.
+                    if (mTracking == TRACK_HS_PALETTE) {
+                        float angle = (float)java.lang.Math.atan2(circlePinnedY, circlePinnedX);
+                        // need to turn angle [-PI ... PI] into unit [0....1]
+                        float unit = angle / (2 * PI);
+
+                        if (unit < 0) {
+                            unit += 1;
+                        }
+
+                        mCoord[0] = round(FloatMath.cos(angle) * radius);
+                        mCoord[1] = round(FloatMath.sin(angle) * radius);
+                        int c = interpColor(mSpectrumColorsRev, unit);
+                        float[] hsv = new float[3];
+                        Color.colorToHSV(c, hsv);
+                        mHSV[0] = hsv[0];
+                        mHSV[1] = radius / PALETTE_RADIUS;
+                        updateAllFromHSV();
+                        mSwatchNew.setColor(Color.HSVToColor(mHSV));
+                        setVerValSlider();
+                        invalidate();
+                    }
+                    else if (mTracking == TRACK_VER_VALUE_SLIDER) {
+                        if (mCoord[2] != y2) {
+                            mCoord[2] = y2;
+                            float value = 1.0f - (float)y2 / (float)PALETTE_DIM;
+                            mHSV[2] = value;
+                            updateAllFromHSV();
+                            mSwatchNew.setColor(Color.HSVToColor(mHSV));
+                            setOvalValDimmer();
+                            invalidate();
+                        }
+                    }
+
+                    break;
+
+                case MotionEvent.ACTION_UP:
+
+                    //NEW_METHOD_WORK_NEEDED_HERE
+                    //To add a new method, replicate and extend the last entry in this list.
+                    if (mTracking == TRACK_SWATCH_OLD && inSwatchOld) {
+                        Color.colorToHSV(mOriginalColor, mHSV);
+                        mSwatchNew.setColor(mOriginalColor);
+                        initUI();
+                        invalidate();
+                    }
+                    else if (mTracking == TRACK_SWATCH_NEW && inSwatchNew) {
+                        mListener.colorChanged(mSwatchNew.getColor());
+                        invalidate();
+                    }
+
+                    mTracking = TRACKED_NONE;
+                    break;
+            }
+
+            return true;
+        }
+    }
+}