diff options
author | Kenny Root <kenny@the-b.org> | 2009-06-23 14:08:37 +0000 |
---|---|---|
committer | Kenny Root <kenny@the-b.org> | 2009-06-23 14:08:37 +0000 |
commit | 1f9a96b88003ea87709e38883b85605c9a108a2c (patch) | |
tree | e7ca6ab5cd0c8aae63a70979e8d2fda39372cf80 | |
parent | 4c8505bda7e529ceb8a6c821c8b5c885bea5436a (diff) | |
download | connectbot-1f9a96b88003ea87709e38883b85605c9a108a2c.tar.gz connectbot-1f9a96b88003ea87709e38883b85605c9a108a2c.tar.bz2 connectbot-1f9a96b88003ea87709e38883b85605c9a108a2c.zip |
Add color customization capabilities
git-svn-id: https://connectbot.googlecode.com/svn/trunk/connectbot@326 df292f66-193f-0410-a5fc-6d59da041ff2
-rw-r--r-- | res/layout/act_colors.xml | 67 | ||||
-rw-r--r-- | res/values/strings.xml | 2 | ||||
-rw-r--r-- | src/org/connectbot/ColorsActivity.java | 297 | ||||
-rw-r--r-- | src/org/connectbot/HostListActivity.java | 4 | ||||
-rw-r--r-- | src/org/connectbot/TerminalView.java | 4 | ||||
-rw-r--r-- | src/org/connectbot/service/TerminalBridge.java | 81 | ||||
-rw-r--r-- | src/org/connectbot/util/Colors.java | 92 | ||||
-rw-r--r-- | src/org/connectbot/util/HostDatabase.java | 221 | ||||
-rw-r--r-- | src/org/connectbot/util/UberColorPickerDialog.java | 3105 |
9 files changed, 3799 insertions, 74 deletions
diff --git a/res/layout/act_colors.xml b/res/layout/act_colors.xml new file mode 100644 index 0000000..f399c7a --- /dev/null +++ b/res/layout/act_colors.xml @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding="utf-8"?> + <!-- + ConnectBot: simple, powerful, open-source SSH client for Android + Copyright (C) 2007-2008 Kenny Root, Jeffrey Sharkey This program is + free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your + option) any later version. This program is distributed in the hope + that it will be useful, but WITHOUT ANY WARRANTY; without even the + implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the GNU General Public License for more details. You + should have received a copy of the GNU General Public License along + with this program. If not, see <http://www.gnu.org/licenses/>. + --> + +<RelativeLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent"> + + <TextView + android:text="FG:" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:id="@+id/fg_label" + android:paddingTop="12dp" + /> + + <Spinner + android:id="@+id/fg" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_toRightOf="@+id/fg_label" + /> + + <Spinner + android:id="@+id/bg" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignTop="@+id/fg" + android:layout_alignParentRight="true" + /> + + <TextView + android:text="BG:" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:layout_toLeftOf="@+id/bg" + android:id="@+id/fg_label" + android:paddingTop="12dp" + /> + + <GridView + android:id="@+id/color_grid" + android:layout_height="fill_parent" + android:layout_width="fill_parent" + android:padding="10dp" + android:verticalSpacing="10dp" + android:horizontalSpacing="10dp" + android:numColumns="auto_fit" + android:columnWidth="60dp" + android:stretchMode="columnWidth" + android:gravity="center" + android:layout_below="@+id/fg" + android:stackFromBottom="true" android:minHeight="60dp"/> + +</RelativeLayout> diff --git a/res/values/strings.xml b/res/values/strings.xml index c29b15c..7631a18 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -13,6 +13,8 @@ <string name="title_host_editor">Edit Host</string> <!-- Window title for Help index --> <string name="title_help">Help</string> + <!-- Window title for color list editing screen --> + <string name="title_colors">Colors</string> <string name="resolve_connect">Connect</string> <string name="resolve_entropy">Gather Entropy</string> diff --git a/src/org/connectbot/ColorsActivity.java b/src/org/connectbot/ColorsActivity.java new file mode 100644 index 0000000..a114e7d --- /dev/null +++ b/src/org/connectbot/ColorsActivity.java @@ -0,0 +1,297 @@ +/* + ConnectBot: simple, powerful, open-source SSH client for Android + Copyright (C) 2007-2008 Kenny Root, Jeffrey Sharkey + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +package org.connectbot; + +import java.util.Arrays; +import java.util.List; + +import org.connectbot.bean.HostBean; +import org.connectbot.util.HostDatabase; +import org.connectbot.util.UberColorPickerDialog; +import org.connectbot.util.UberColorPickerDialog.OnColorChangedListener; + +import android.app.Activity; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.os.Bundle; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.BaseAdapter; +import android.widget.GridView; +import android.widget.Spinner; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.AdapterView.OnItemSelectedListener; + +/** + * @author Kenny Root + * + */ +public class ColorsActivity extends Activity implements OnItemClickListener, OnColorChangedListener, OnItemSelectedListener { + private GridView mColorGrid; + private Spinner mFgSpinner; + private Spinner mBgSpinner; + + private HostBean mHost; + + private List<Integer> mColorList; + private HostDatabase hostdb; + + private int mCurrentColor = 0; + + private int[] mDefaultColors; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.act_colors); + + this.setTitle(String.format("%s: %s", + getResources().getText(R.string.app_name), + getResources().getText(R.string.title_colors))); + + mHost = null; + + hostdb = new HostDatabase(this); + + mColorList = Arrays.asList(hostdb.getColorsForHost(mHost)); + mDefaultColors = hostdb.getDefaultColorsForHost(mHost); + + mColorGrid = (GridView) findViewById(R.id.color_grid); + mColorGrid.setAdapter(new ColorsAdapter(true)); + mColorGrid.setOnItemClickListener(this); + + mFgSpinner = (Spinner) findViewById(R.id.fg); + mFgSpinner.setAdapter(new ColorsAdapter(false)); + mFgSpinner.setSelection(mDefaultColors[0]); + mFgSpinner.setOnItemSelectedListener(this); + + mBgSpinner = (Spinner) findViewById(R.id.bg); + mBgSpinner.setAdapter(new ColorsAdapter(false)); + mBgSpinner.setSelection(mDefaultColors[1]); + mBgSpinner.setOnItemSelectedListener(this); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + + if (hostdb != null) { + hostdb.close(); + hostdb = null; + } + } + + @Override + protected void onResume() { + super.onResume(); + + if (hostdb == null) + hostdb = new HostDatabase(this); + } + + private class ColorsAdapter extends BaseAdapter { + private boolean mSquareViews; + + public ColorsAdapter(boolean squareViews) { + mSquareViews = squareViews; + } + + public View getView(int position, View convertView, ViewGroup parent) { + ColorView c; + + if (convertView == null) { + c = new ColorView(ColorsActivity.this, mSquareViews); + } else { + c = (ColorView) convertView; + } + + c.setColor(mColorList.get(position)); + c.setNumber(position + 1); + + return c; + } + + public int getCount() { + return mColorList.size(); + } + + public Object getItem(int position) { + return mColorList.get(position); + } + + public long getItemId(int position) { + return position; + } + } + + private class ColorView extends View { + private boolean mSquare; + + private Paint mTextPaint; + private Paint mShadowPaint; + + // Things we paint + private int mBackgroundColor; + private String mText; + + private int mAscent; + private int mWidthCenter; + private int mHeightCenter; + + public ColorView(Context context, boolean square) { + super(context); + + mSquare = square; + + mTextPaint = new Paint(); + mTextPaint.setAntiAlias(true); + mTextPaint.setTextSize(16); + mTextPaint.setColor(0xFFFFFFFF); + mTextPaint.setTextAlign(Paint.Align.CENTER); + + mShadowPaint = new Paint(mTextPaint); + mShadowPaint.setStyle(Paint.Style.STROKE); + mShadowPaint.setStrokeCap(Paint.Cap.ROUND); + mShadowPaint.setStrokeJoin(Paint.Join.ROUND); + mShadowPaint.setStrokeWidth(4f); + mShadowPaint.setColor(0xFF000000); + + setPadding(10, 10, 10, 10); + } + + public void setColor(int color) { + mBackgroundColor = color; + } + + public void setNumber(int number) { + mText = Integer.toString(number); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = measureWidth(widthMeasureSpec); + + int height; + if (mSquare) + height = width; + else + height = measureHeight(heightMeasureSpec); + + mAscent = (int) mTextPaint.ascent(); + mWidthCenter = width / 2; + mHeightCenter = height / 2 - mAscent / 2; + + setMeasuredDimension(width, height); + } + + private int measureWidth(int measureSpec) { + int result = 0; + int specMode = MeasureSpec.getMode(measureSpec); + int specSize = MeasureSpec.getSize(measureSpec); + + if (specMode == MeasureSpec.EXACTLY) { + // We were told how big to be + result = specSize; + } else { + // Measure the text + result = (int) mTextPaint.measureText(mText) + getPaddingLeft() + + getPaddingRight(); + if (specMode == MeasureSpec.AT_MOST) { + // Respect AT_MOST value if that was what is called for by + // measureSpec + result = Math.min(result, specSize); + } + } + + return result; + } + + private int measureHeight(int measureSpec) { + int result = 0; + int specMode = MeasureSpec.getMode(measureSpec); + int specSize = MeasureSpec.getSize(measureSpec); + + mAscent = (int) mTextPaint.ascent(); + if (specMode == MeasureSpec.EXACTLY) { + // We were told how big to be + result = specSize; + } else { + // Measure the text (beware: ascent is a negative number) + result = (int) (-mAscent + mTextPaint.descent()) + + getPaddingTop() + getPaddingBottom(); + if (specMode == MeasureSpec.AT_MOST) { + // Respect AT_MOST value if that was what is called for by + // measureSpec + result = Math.min(result, specSize); + } + } + return result; + } + + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + canvas.drawColor(mBackgroundColor); + + canvas.drawText(mText, mWidthCenter, mHeightCenter, mShadowPaint); + canvas.drawText(mText, mWidthCenter, mHeightCenter, mTextPaint); + } + } + + private void editColor(int colorNumber) { + mCurrentColor = colorNumber; + new UberColorPickerDialog(this, this, mColorList.get(colorNumber), true).show(); + } + + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + editColor(position); + } + + public void onNothingSelected(AdapterView<?> arg0) { } + + public void colorChanged(int value) { + hostdb.setGlobalColor(mCurrentColor, value); + mColorList.set(mCurrentColor, value); + mColorGrid.invalidateViews(); + } + + public void onItemSelected(AdapterView<?> parent, View view, int position, + long id) { + boolean needUpdate = false; + if (parent == mFgSpinner) { + if (position != mDefaultColors[0]) { + mDefaultColors[0] = position; + needUpdate = true; + } + } else if (parent == mBgSpinner) { + if (position != mDefaultColors[1]) { + mDefaultColors[1] = position; + needUpdate = true; + } + } + + if (needUpdate) + hostdb.setDefaultColorsForHost(mHost, mDefaultColors[0], mDefaultColors[1]); + } +} diff --git a/src/org/connectbot/HostListActivity.java b/src/org/connectbot/HostListActivity.java index 48ac235..26d0c2f 100644 --- a/src/org/connectbot/HostListActivity.java +++ b/src/org/connectbot/HostListActivity.java @@ -308,6 +308,10 @@ public class HostListActivity extends ListActivity { keys.setIcon(android.R.drawable.ic_lock_lock); keys.setIntent(new Intent(HostListActivity.this, PubkeyListActivity.class)); + MenuItem colors = menu.add("Colors"); + colors.setIcon(android.R.drawable.ic_menu_slideshow); + colors.setIntent(new Intent(HostListActivity.this, ColorsActivity.class)); + MenuItem settings = menu.add(R.string.list_menu_settings); settings.setIcon(android.R.drawable.ic_menu_preferences); settings.setIntent(new Intent(HostListActivity.this, SettingsActivity.class)); diff --git a/src/org/connectbot/TerminalView.java b/src/org/connectbot/TerminalView.java index 5bff54e..fa43eaa 100644 --- a/src/org/connectbot/TerminalView.java +++ b/src/org/connectbot/TerminalView.java @@ -74,8 +74,8 @@ public class TerminalView extends View implements FontSizeChangedListener { setFocusableInTouchMode(true); cursorPaint = new Paint(); - cursorPaint.setColor(bridge.color[TerminalBridge.COLOR_FG_STD]); - cursorPaint.setXfermode(new PixelXorXfermode(bridge.color[TerminalBridge.COLOR_BG_STD])); + cursorPaint.setColor(bridge.color[bridge.defaultFg]); + cursorPaint.setXfermode(new PixelXorXfermode(bridge.color[bridge.defaultBg])); cursorPaint.setAntiAlias(true); cursorStrokePaint = new Paint(cursorPaint); diff --git a/src/org/connectbot/service/TerminalBridge.java b/src/org/connectbot/service/TerminalBridge.java index f81e3a5..28fd821 100644 --- a/src/org/connectbot/service/TerminalBridge.java +++ b/src/org/connectbot/service/TerminalBridge.java @@ -65,10 +65,10 @@ public class TerminalBridge implements VDUDisplay, OnKeyListener { public final static int DEFAULT_FONT_SIZE = 10; - public int color[]; + public Integer[] color; - public final static int COLOR_FG_STD = 7; - public final static int COLOR_BG_STD = 0; + public int defaultFg = HostDatabase.DEFAULT_FG_COLOR; + public int defaultBg = HostDatabase.DEFAULT_BG_COLOR; protected final TerminalManager manager; @@ -985,8 +985,8 @@ public class TerminalBridge implements VDUDisplay, OnKeyListener { int currAttr = buffer.charAttributes[buffer.windowBase + l][c]; // reset default colors - fg = color[COLOR_FG_STD]; - bg = color[COLOR_BG_STD]; + fg = color[defaultFg]; + bg = color[defaultBg]; // check if foreground color attribute is set if ((currAttr & VDUBuffer.COLOR_FG) != 0) { @@ -1206,71 +1206,10 @@ public class TerminalBridge implements VDUDisplay, OnKeyListener { } public final void resetColors() { - color = new int[] { - 0xff000000, // black - 0xffcc0000, // red - 0xff00cc00, // green - 0xffcccc00, // brown - 0xff0000cc, // blue - 0xffcc00cc, // purple - 0xff00cccc, // cyan - 0xffcccccc, // light grey - 0xff444444, // dark grey - 0xffff4444, // light red - 0xff44ff44, // light green - 0xffffff44, // yellow - 0xff4444ff, // light blue - 0xffff44ff, // light purple - 0xff44ffff, // light cyan - 0xffffffff, // white - 0xff000000, 0xff00005f, 0xff000087, 0xff0000af, 0xff0000d7, - 0xff0000ff, 0xff005f00, 0xff005f5f, 0xff005f87, 0xff005faf, - 0xff005fd7, 0xff005fff, 0xff008700, 0xff00875f, 0xff008787, - 0xff0087af, 0xff0087d7, 0xff0087ff, 0xff00af00, 0xff00af5f, - 0xff00af87, 0xff00afaf, 0xff00afd7, 0xff00afff, 0xff00d700, - 0xff00d75f, 0xff00d787, 0xff00d7af, 0xff00d7d7, 0xff00d7ff, - 0xff00ff00, 0xff00ff5f, 0xff00ff87, 0xff00ffaf, 0xff00ffd7, - 0xff00ffff, 0xff5f0000, 0xff5f005f, 0xff5f0087, 0xff5f00af, - 0xff5f00d7, 0xff5f00ff, 0xff5f5f00, 0xff5f5f5f, 0xff5f5f87, - 0xff5f5faf, 0xff5f5fd7, 0xff5f5fff, 0xff5f8700, 0xff5f875f, - 0xff5f8787, 0xff5f87af, 0xff5f87d7, 0xff5f87ff, 0xff5faf00, - 0xff5faf5f, 0xff5faf87, 0xff5fafaf, 0xff5fafd7, 0xff5fafff, - 0xff5fd700, 0xff5fd75f, 0xff5fd787, 0xff5fd7af, 0xff5fd7d7, - 0xff5fd7ff, 0xff5fff00, 0xff5fff5f, 0xff5fff87, 0xff5fffaf, - 0xff5fffd7, 0xff5fffff, 0xff870000, 0xff87005f, 0xff870087, - 0xff8700af, 0xff8700d7, 0xff8700ff, 0xff875f00, 0xff875f5f, - 0xff875f87, 0xff875faf, 0xff875fd7, 0xff875fff, 0xff878700, - 0xff87875f, 0xff878787, 0xff8787af, 0xff8787d7, 0xff8787ff, - 0xff87af00, 0xff87af5f, 0xff87af87, 0xff87afaf, 0xff87afd7, - 0xff87afff, 0xff87d700, 0xff87d75f, 0xff87d787, 0xff87d7af, - 0xff87d7d7, 0xff87d7ff, 0xff87ff00, 0xff87ff5f, 0xff87ff87, - 0xff87ffaf, 0xff87ffd7, 0xff87ffff, 0xffaf0000, 0xffaf005f, - 0xffaf0087, 0xffaf00af, 0xffaf00d7, 0xffaf00ff, 0xffaf5f00, - 0xffaf5f5f, 0xffaf5f87, 0xffaf5faf, 0xffaf5fd7, 0xffaf5fff, - 0xffaf8700, 0xffaf875f, 0xffaf8787, 0xffaf87af, 0xffaf87d7, - 0xffaf87ff, 0xffafaf00, 0xffafaf5f, 0xffafaf87, 0xffafafaf, - 0xffafafd7, 0xffafafff, 0xffafd700, 0xffafd75f, 0xffafd787, - 0xffafd7af, 0xffafd7d7, 0xffafd7ff, 0xffafff00, 0xffafff5f, - 0xffafff87, 0xffafffaf, 0xffafffd7, 0xffafffff, 0xffd70000, - 0xffd7005f, 0xffd70087, 0xffd700af, 0xffd700d7, 0xffd700ff, - 0xffd75f00, 0xffd75f5f, 0xffd75f87, 0xffd75faf, 0xffd75fd7, - 0xffd75fff, 0xffd78700, 0xffd7875f, 0xffd78787, 0xffd787af, - 0xffd787d7, 0xffd787ff, 0xffd7af00, 0xffd7af5f, 0xffd7af87, - 0xffd7afaf, 0xffd7afd7, 0xffd7afff, 0xffd7d700, 0xffd7d75f, - 0xffd7d787, 0xffd7d7af, 0xffd7d7d7, 0xffd7d7ff, 0xffd7ff00, - 0xffd7ff5f, 0xffd7ff87, 0xffd7ffaf, 0xffd7ffd7, 0xffd7ffff, - 0xffff0000, 0xffff005f, 0xffff0087, 0xffff00af, 0xffff00d7, - 0xffff00ff, 0xffff5f00, 0xffff5f5f, 0xffff5f87, 0xffff5faf, - 0xffff5fd7, 0xffff5fff, 0xffff8700, 0xffff875f, 0xffff8787, - 0xffff87af, 0xffff87d7, 0xffff87ff, 0xffffaf00, 0xffffaf5f, - 0xffffaf87, 0xffffafaf, 0xffffafd7, 0xffffafff, 0xffffd700, - 0xffffd75f, 0xffffd787, 0xffffd7af, 0xffffd7d7, 0xffffd7ff, - 0xffffff00, 0xffffff5f, 0xffffff87, 0xffffffaf, 0xffffffd7, - 0xffffffff, 0xff080808, 0xff121212, 0xff1c1c1c, 0xff262626, - 0xff303030, 0xff3a3a3a, 0xff444444, 0xff4e4e4e, 0xff585858, - 0xff626262, 0xff6c6c6c, 0xff767676, 0xff808080, 0xff8a8a8a, - 0xff949494, 0xff9e9e9e, 0xffa8a8a8, 0xffb2b2b2, 0xffbcbcbc, - 0xffc6c6c6, 0xffd0d0d0, 0xffdadada, 0xffe4e4e4, 0xffeeeeee, - }; + int[] defaults = manager.hostdb.getDefaultColorsForHost(host); + defaultFg = defaults[0]; + defaultBg = defaults[1]; + + color = manager.hostdb.getColorsForHost(host); } } diff --git a/src/org/connectbot/util/Colors.java b/src/org/connectbot/util/Colors.java new file mode 100644 index 0000000..6044b96 --- /dev/null +++ b/src/org/connectbot/util/Colors.java @@ -0,0 +1,92 @@ +/* + ConnectBot: simple, powerful, open-source SSH client for Android + Copyright (C) 2007-2008 Kenny Root, Jeffrey Sharkey + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +package org.connectbot.util; + +/** + * @author Kenny Root + * + */ +public class Colors { + public final static Integer[] defaults = new Integer[] { + 0xff000000, // black + 0xffcc0000, // red + 0xff00cc00, // green + 0xffcccc00, // brown + 0xff0000cc, // blue + 0xffcc00cc, // purple + 0xff00cccc, // cyan + 0xffcccccc, // light grey + 0xff444444, // dark grey + 0xffff4444, // light red + 0xff44ff44, // light green + 0xffffff44, // yellow + 0xff4444ff, // light blue + 0xffff44ff, // light purple + 0xff44ffff, // light cyan + 0xffffffff, // white + 0xff000000, 0xff00005f, 0xff000087, 0xff0000af, 0xff0000d7, + 0xff0000ff, 0xff005f00, 0xff005f5f, 0xff005f87, 0xff005faf, + 0xff005fd7, 0xff005fff, 0xff008700, 0xff00875f, 0xff008787, + 0xff0087af, 0xff0087d7, 0xff0087ff, 0xff00af00, 0xff00af5f, + 0xff00af87, 0xff00afaf, 0xff00afd7, 0xff00afff, 0xff00d700, + 0xff00d75f, 0xff00d787, 0xff00d7af, 0xff00d7d7, 0xff00d7ff, + 0xff00ff00, 0xff00ff5f, 0xff00ff87, 0xff00ffaf, 0xff00ffd7, + 0xff00ffff, 0xff5f0000, 0xff5f005f, 0xff5f0087, 0xff5f00af, + 0xff5f00d7, 0xff5f00ff, 0xff5f5f00, 0xff5f5f5f, 0xff5f5f87, + 0xff5f5faf, 0xff5f5fd7, 0xff5f5fff, 0xff5f8700, 0xff5f875f, + 0xff5f8787, 0xff5f87af, 0xff5f87d7, 0xff5f87ff, 0xff5faf00, + 0xff5faf5f, 0xff5faf87, 0xff5fafaf, 0xff5fafd7, 0xff5fafff, + 0xff5fd700, 0xff5fd75f, 0xff5fd787, 0xff5fd7af, 0xff5fd7d7, + 0xff5fd7ff, 0xff5fff00, 0xff5fff5f, 0xff5fff87, 0xff5fffaf, + 0xff5fffd7, 0xff5fffff, 0xff870000, 0xff87005f, 0xff870087, + 0xff8700af, 0xff8700d7, 0xff8700ff, 0xff875f00, 0xff875f5f, + 0xff875f87, 0xff875faf, 0xff875fd7, 0xff875fff, 0xff878700, + 0xff87875f, 0xff878787, 0xff8787af, 0xff8787d7, 0xff8787ff, + 0xff87af00, 0xff87af5f, 0xff87af87, 0xff87afaf, 0xff87afd7, + 0xff87afff, 0xff87d700, 0xff87d75f, 0xff87d787, 0xff87d7af, + 0xff87d7d7, 0xff87d7ff, 0xff87ff00, 0xff87ff5f, 0xff87ff87, + 0xff87ffaf, 0xff87ffd7, 0xff87ffff, 0xffaf0000, 0xffaf005f, + 0xffaf0087, 0xffaf00af, 0xffaf00d7, 0xffaf00ff, 0xffaf5f00, + 0xffaf5f5f, 0xffaf5f87, 0xffaf5faf, 0xffaf5fd7, 0xffaf5fff, + 0xffaf8700, 0xffaf875f, 0xffaf8787, 0xffaf87af, 0xffaf87d7, + 0xffaf87ff, 0xffafaf00, 0xffafaf5f, 0xffafaf87, 0xffafafaf, + 0xffafafd7, 0xffafafff, 0xffafd700, 0xffafd75f, 0xffafd787, + 0xffafd7af, 0xffafd7d7, 0xffafd7ff, 0xffafff00, 0xffafff5f, + 0xffafff87, 0xffafffaf, 0xffafffd7, 0xffafffff, 0xffd70000, + 0xffd7005f, 0xffd70087, 0xffd700af, 0xffd700d7, 0xffd700ff, + 0xffd75f00, 0xffd75f5f, 0xffd75f87, 0xffd75faf, 0xffd75fd7, + 0xffd75fff, 0xffd78700, 0xffd7875f, 0xffd78787, 0xffd787af, + 0xffd787d7, 0xffd787ff, 0xffd7af00, 0xffd7af5f, 0xffd7af87, + 0xffd7afaf, 0xffd7afd7, 0xffd7afff, 0xffd7d700, 0xffd7d75f, + 0xffd7d787, 0xffd7d7af, 0xffd7d7d7, 0xffd7d7ff, 0xffd7ff00, + 0xffd7ff5f, 0xffd7ff87, 0xffd7ffaf, 0xffd7ffd7, 0xffd7ffff, + 0xffff0000, 0xffff005f, 0xffff0087, 0xffff00af, 0xffff00d7, + 0xffff00ff, 0xffff5f00, 0xffff5f5f, 0xffff5f87, 0xffff5faf, + 0xffff5fd7, 0xffff5fff, 0xffff8700, 0xffff875f, 0xffff8787, + 0xffff87af, 0xffff87d7, 0xffff87ff, 0xffffaf00, 0xffffaf5f, + 0xffffaf87, 0xffffafaf, 0xffffafd7, 0xffffafff, 0xffffd700, + 0xffffd75f, 0xffffd787, 0xffffd7af, 0xffffd7d7, 0xffffd7ff, + 0xffffff00, 0xffffff5f, 0xffffff87, 0xffffffaf, 0xffffffd7, + 0xffffffff, 0xff080808, 0xff121212, 0xff1c1c1c, 0xff262626, + 0xff303030, 0xff3a3a3a, 0xff444444, 0xff4e4e4e, 0xff585858, + 0xff626262, 0xff6c6c6c, 0xff767676, 0xff808080, 0xff8a8a8a, + 0xff949494, 0xff9e9e9e, 0xffa8a8a8, 0xffb2b2b2, 0xffbcbcbc, + 0xffc6c6c6, 0xffd0d0d0, 0xffdadada, 0xffe4e4e4, 0xffeeeeee, + }; +} diff --git a/src/org/connectbot/util/HostDatabase.java b/src/org/connectbot/util/HostDatabase.java index f8a4e3f..8f6fc02 100644 --- a/src/org/connectbot/util/HostDatabase.java +++ b/src/org/connectbot/util/HostDatabase.java @@ -49,7 +49,7 @@ public class HostDatabase extends SQLiteOpenHelper { public final static String TAG = "ConnectBot.HostDatabase"; public final static String DB_NAME = "hosts"; - public final static int DB_VERSION = 17; + public final static int DB_VERSION = 18; public final static String TABLE_HOSTS = "hosts"; public final static String FIELD_HOST_NICKNAME = "nickname"; @@ -77,6 +77,18 @@ public class HostDatabase extends SQLiteOpenHelper { public final static String FIELD_PORTFORWARD_DESTADDR = "destaddr"; public final static String FIELD_PORTFORWARD_DESTPORT = "destport"; + public final static String TABLE_COLORS = "colors"; + public final static String FIELD_COLOR_SCHEME = "scheme"; + public final static String FIELD_COLOR_NUMBER = "number"; + public final static String FIELD_COLOR_VALUE = "value"; + + public final static String TABLE_COLOR_DEFAULTS = "colorDefaults"; + public final static String FIELD_COLOR_FG = "fg"; + public final static String FIELD_COLOR_BG = "bg"; + + public final static int DEFAULT_FG_COLOR = 7; + public final static int DEFAULT_BG_COLOR = 0; + public final static String COLOR_RED = "red"; public final static String COLOR_GREEN = "green"; public final static String COLOR_BLUE = "blue"; @@ -132,6 +144,27 @@ public class HostDatabase extends SQLiteOpenHelper { + FIELD_PORTFORWARD_SOURCEPORT + " INTEGER NOT NULL DEFAULT 8080, " + FIELD_PORTFORWARD_DESTADDR + " TEXT, " + FIELD_PORTFORWARD_DESTPORT + " TEXT)"); + + db.execSQL("CREATE INDEX " + TABLE_PORTFORWARDS + FIELD_PORTFORWARD_HOSTID + "index ON " + + TABLE_PORTFORWARDS + " (" + FIELD_PORTFORWARD_HOSTID + ");"); + + db.execSQL("CREATE TABLE " + TABLE_COLORS + + " (_id INTEGER PRIMARY KEY, " + + FIELD_COLOR_NUMBER + " INTEGER, " + + FIELD_COLOR_VALUE + " INTEGER, " + + FIELD_COLOR_SCHEME + " INTEGER)"); + + db.execSQL("CREATE INDEX " + TABLE_COLORS + FIELD_COLOR_SCHEME + "index ON " + + TABLE_COLORS + " (" + FIELD_COLOR_SCHEME + ");"); + + db.execSQL("CREATE TABLE " + TABLE_COLOR_DEFAULTS + + " (_id INTEGER PRIMARY KEY, " + + FIELD_COLOR_SCHEME + " INTEGER, " + + FIELD_COLOR_FG + " INTEGER, " + + FIELD_COLOR_BG + " INTEGER)"); + + db.execSQL("CREATE INDEX " + TABLE_COLOR_DEFAULTS + FIELD_COLOR_SCHEME + "index ON " + + TABLE_COLOR_DEFAULTS + " (" + FIELD_COLOR_SCHEME + ");"); } @Override @@ -173,6 +206,26 @@ public class HostDatabase extends SQLiteOpenHelper { case 16: db.execSQL("ALTER TABLE " + TABLE_HOSTS + " ADD COLUMN " + FIELD_HOST_DELKEY + " TEXT DEFAULT '" + DELKEY_DEL + "'"); + case 17: + db.execSQL("CREATE INDEX " + TABLE_PORTFORWARDS + FIELD_PORTFORWARD_HOSTID + "index ON " + + TABLE_PORTFORWARDS + " (" + FIELD_PORTFORWARD_HOSTID + ");"); + + // Add colors + db.execSQL("CREATE TABLE " + TABLE_COLORS + + " (_id INTEGER PRIMARY KEY, " + + FIELD_COLOR_NUMBER + " INTEGER, " + + FIELD_COLOR_VALUE + " INTEGER, " + + FIELD_COLOR_SCHEME + " INTEGER)"); + db.execSQL("CREATE INDEX " + TABLE_COLORS + FIELD_COLOR_SCHEME + "index ON " + + TABLE_COLORS + " (" + FIELD_COLOR_SCHEME + ");"); + + db.execSQL("CREATE TABLE " + TABLE_COLOR_DEFAULTS + + " (_id INTEGER PRIMARY KEY, " + + FIELD_COLOR_SCHEME + " INTEGER, " + + FIELD_COLOR_FG + " INTEGER, " + + FIELD_COLOR_BG + " INTEGER)"); + db.execSQL("CREATE INDEX " + TABLE_COLOR_DEFAULTS + FIELD_COLOR_SCHEME + "index ON " + + TABLE_COLOR_DEFAULTS + " (" + FIELD_COLOR_SCHEME + ");"); } } catch (SQLiteException e) { // The database has entered an unknown state. Try to recover. @@ -583,4 +636,170 @@ public class HostDatabase extends SQLiteOpenHelper { db.delete(TABLE_PORTFORWARDS, "_id = ?", new String[] { String.valueOf(pfb.getId()) }); db.close(); } + + public Integer[] getColorsForHost(HostBean host) { + Integer[] colors = Colors.defaults.clone(); + + SQLiteDatabase db = getReadableDatabase(); + + Cursor c = db.query(TABLE_COLORS, new String[] { + FIELD_COLOR_NUMBER, FIELD_COLOR_VALUE }, + FIELD_COLOR_SCHEME + " IS NULL", + null, null, null, null); + + while (c.moveToNext()) { + Log.d(TAG, "Setting default color " + c.getInt(0) + " to " + c.getInt(1)); + colors[c.getInt(0)] = new Integer(c.getInt(1)); + } + + c.close(); + + // TODO This could probably just be a join + if (host != null) { + c = db.query(TABLE_COLORS, new String[] { + FIELD_COLOR_NUMBER, FIELD_COLOR_VALUE }, + FIELD_COLOR_SCHEME + " = ?", + new String[] { String.valueOf(host.getId()) }, + null, null, null); + + while (c.moveToNext()) { + colors[c.getInt(0)] = new Integer(c.getInt(1)); + } + + c.close(); + } + + db.close(); + + return colors; + } + + public void setColorForHost(HostBean host, int number, int value) { + SQLiteDatabase db = getWritableDatabase(); + + String hostWhere; + if (host == null) + hostWhere = FIELD_COLOR_SCHEME + " IS NULL"; + else + hostWhere = FIELD_COLOR_SCHEME + " = ?"; + + if (value == Colors.defaults[number]) { + String[] whereArgs; + + if (host != null) { + whereArgs = new String[2]; + whereArgs[1] = String.valueOf(host.getId()); + } else + whereArgs = new String[1]; + + whereArgs[0] = String.valueOf(number); + + db.delete(TABLE_COLORS, + FIELD_COLOR_NUMBER + " = ? AND " + + hostWhere, + new String[] { String.valueOf(number) }); + } else { + ContentValues values = new ContentValues(); + values.put(FIELD_COLOR_NUMBER, number); + values.put(FIELD_COLOR_VALUE, value); + + String[] whereArgs = null; + + if (host != null) + whereArgs = new String[] { String.valueOf(host.getId()) }; + + int rowsAffected = db.update(TABLE_COLORS, values, + hostWhere, whereArgs); + + if (rowsAffected == 0) { + db.insert(TABLE_COLORS, null, values); + } + } + + db.close(); + } + + public void setGlobalColor(int number, int value) { + setColorForHost(null, number, value); + } + + public int[] getDefaultColorsForHost(HostBean host) { + int[] colors = new int[] { DEFAULT_FG_COLOR, DEFAULT_BG_COLOR }; + + SQLiteDatabase db = getReadableDatabase(); + + Cursor c = db.query(TABLE_COLOR_DEFAULTS, + new String[] { FIELD_COLOR_FG, FIELD_COLOR_BG }, + FIELD_COLOR_SCHEME + " IS NULL", + null, null, null, null); + + if (c.moveToFirst()) { + colors[0] = c.getInt(0); + colors[1] = c.getInt(1); + } + + c.close(); + + // TODO This could probably just be a join + if (host != null) { + c = db.query(TABLE_COLOR_DEFAULTS, + new String[] { FIELD_COLOR_FG, FIELD_COLOR_BG }, + FIELD_COLOR_SCHEME + " = ?", + new String[] { String.valueOf(host.getId()) }, + null, null, null); + + if (c.moveToFirst()) { + colors[0] = c.getInt(0); + colors[1] = c.getInt(1); + } + + c.close(); + } + + db.close(); + + return colors; + } + + public int[] getGlobalDefaultColors() { + return getDefaultColorsForHost(null); + } + + public void setDefaultColorsForHost(HostBean host, int fg, int bg) { + int[] defaultColors = getGlobalDefaultColors(); + + SQLiteDatabase db = getWritableDatabase(); + + String schemeWhere = null; + String[] whereArgs; + + // TODO change host.getId() into scheme numbers for colors + + if (host == null) { + schemeWhere = FIELD_COLOR_SCHEME + " IS NULL"; + whereArgs = null; + } else { + schemeWhere = FIELD_COLOR_SCHEME + " = ?"; + whereArgs = new String[] { String.valueOf(host.getId()) }; + } + + if (fg == defaultColors[0] && bg == defaultColors[1]) { + db.delete(TABLE_COLOR_DEFAULTS, + schemeWhere, + whereArgs); + } else { + ContentValues values = new ContentValues(); + values.put(FIELD_COLOR_FG, fg); + values.put(FIELD_COLOR_BG, bg); + + int rowsAffected = db.update(TABLE_COLOR_DEFAULTS, values, + schemeWhere, whereArgs); + + if (rowsAffected == 0) { + db.insert(TABLE_COLOR_DEFAULTS, null, values); + } + } + + db.close(); + } } diff --git a/src/org/connectbot/util/UberColorPickerDialog.java b/src/org/connectbot/util/UberColorPickerDialog.java new file mode 100644 index 0000000..e310f3b --- /dev/null +++ b/src/org/connectbot/util/UberColorPickerDialog.java @@ -0,0 +1,3105 @@ +/* + * 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 org.connectbot.util; + +import java.util.Calendar; + +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.LinearGradient; +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.view.MotionEvent; +import android.view.View; +import android.view.Window; +import android.widget.Toast; + +/** + * UberColorPickerDialog is a seriously enhanced version of the UberColorPickerDialog class provided in the Android API Demos. + * Improvements include: + * <ul> + * <li> Multiple color spaces and chooser methods (dimension combinations) for manipulating those color spaces, including: + * <ul> + * <li> HSV with angular H and radial S combined in 2D and a 1D V slider. + * <li> HSV with angular H and radial V combined in 2D and a 1D S slider (this one's kinda silly). + * <li> HSV with cardinal S and V combined in 2D and a 1D H slider. + * <li> YUV with cardinal U and V combined in 2D and a 1D Y slider. + * <li> RGB with three 1D sliders. + * <li> HSV with three 1D sliders. + * </ul> + * <li> Simple switch-based compile-time configuration of which chooser methods are provided (search for ENABLED_METHODS near the top of the code). + * <li> Numerical feedback of precise color values. + * <li> Two sample swatches, one the original which can be used to revert to the initial color, the other to show the currently chosen color. + * <li> Trackball input for precise color control. + * <li> Automatic detection of portrait/landscape orientation and adjustment of the widget layout to make best use of that orientation. + * <li> The option of showing or hiding the window title. Showing it wastes a lot of space of course, hiding it is augmented with an introductory toast message. + * <li> Realtime feedback of color changes, not only in the sample swatch but also in all relevant palettes and sliders. + * <li> Hilighted borders to show which widget has trackball focus. + * <li> Position markers on the palettes and sliders to show the current value in each dimension (the value of each parameter). + * </ul> + * <p> + * Version History: + * <ul> + * <li>v1.1, 090408 + * <ul> + * <li>Added hex numerical output (HTML colors). + * <li>All colorspace parameters (HSV, RGB, YUV, Hex) are now updated and shown at all times. + * <li>Converted to GradientDrawable Bitmaps for drawing the 1D sliders. They're much smoother, less blocky. Note that 2D palettes are still constructed from the less smooth gradients. + * <li>Did some general refactoring. + * <li>Made the UV palette slightly more color-accurate (a little ligher and darker at extreme Y values). + * <li>Added a "hilighted" border around the currently selected color chooser method. + * </ul> + * <li>v1.0, 090405 + * <ul> + * <li>First public release + * </ul> + * </ul> + * + * @author Keith Wiley, kwiley@keithwiley.com, http://keithwiley.com + */ +public class UberColorPickerDialog extends Dialog { + private OnColorChangedListener mListener; + private int mInitialColor; + private boolean mShowTitle; + + /** + * 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, + boolean showTitle) { + super(context); + + mListener = listener; + mInitialColor = initialColor; + mShowTitle = showTitle; + } + + /** + * 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; + + if (!mShowTitle) { + getWindow().requestFeature(Window.FEATURE_NO_TITLE); + Toast.makeText(getContext(), "Pick a color (try the trackball)", Toast.LENGTH_SHORT).show(); + } + else 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> + } + } + + /** + * Android's Color.colorToHSV() has what I assume is a bug, such that on a desaturated color it sets H,S,V all to V. + * While ambiguous w.r.t. hue, saturation should certainly be 0 in such a case. Detect and fix. + * @param color 4-byte ARGB + * @return true if fully desaturated, indicating that if this color was passed to Color.colorToHSV(), then the resulting HSV's S should be explicitly set to 0 + */ + static public boolean isGray(int color) { + return (((color >> 16) & 0x00000000FF) == (color & 0x000000FF) + && ((color >> 8) & 0x00000000FF) == (color & 0x000000FF)); + } + + /** + * Android's Color.colorToHSV() has what I assume is a bug, such that on a desaturated color it sets H,S,V all to V. + * While ambiguous w.r.t. hue, saturation should certainly be 0 in such a case. Detect and fix. + * @param color 4-elm rgb of indeterminate range + * @return true if fully desaturated, indicating that if this color was passed to Color.colorToHSV(), then the resulting HSV's S should be explicitly set to 0 + */ + static public boolean isGray(int[] rgb) { + return (rgb[1] == rgb[0] && rgb[2] == rgb[0]); + } + + /** + * 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 FIRST_HOR_SLIDER_POS_Y = 20; + + private static final int SLIDER_THICKNESS = 40; + + private static final int METHOD_SELECTOR_SIZE = 40; + private static final int METHOD_SELECTOR_SPACING = 10; + private static int METHOD_SELECTOR_POS_X = PALETTE_DIM + METHOD_SELECTOR_SPACING; + + private static int VIEW_DIM_X = PALETTE_DIM + METHOD_SELECTOR_SPACING + METHOD_SELECTOR_SIZE; + private static int VIEW_DIM_Y = METHOD_SELECTOR_SIZE * 5 + METHOD_SELECTOR_SIZE * 2; + + //NEW_METHOD_WORK_NEEDED_HERE + private static final int METHOD_HS_V_PALETTE = 0; + /** + * METHOD_HV_S_PALETTE is pretty silly in that it violates the dimension-interdependence of HSV, + * thus making it difficult to use (as the user moves through the 2D palette its coloration actually changes! + * It's just thrown in for fun. + */ + private static final int METHOD_HV_S_PALETTE = 1; + private static final int METHOD_SV_H_PALETTE = 2; + /** + * Please read the important note at setUVPalette() about the visual portrayal of the UV palette. + */ + private static final int METHOD_UV_Y_PALETTE = 3; + private static final int METHOD_RGB_SLIDERS = 4; + private static final int METHOD_HSV_SLIDERS = 5; + private static final int METHOD_YUV_SLIDERS = 6; + + /** + * Edit these switches to show/hide each method's icon in the method selector list and thus to enable/disable access to that method. + */ + //NEW_METHOD_WORK_NEEDED_HERE + //Add a new entry to the list, make sure you keep the order correct w.r.t. to the METHOD_ consts. + private static final boolean[] ENABLED_METHODS = { + true, //METHOD_HS_V_ENABLED + true, //METHOD_HV_S_ENABLED + true, //METHOD_SV_H_ENABLED + true, //METHOD_UV_Y_ENABLED + true, //METHOD_RGB_ENABLED + true, //METHOD_HSV_ENABLED + true //METHOD_YUV_SLIDERS + }; + + //No need to manually keep this in sync with the switches above, it will be adjusted automatically during setup. + private static int NUM_ENABLED_METHODS = ENABLED_METHODS.length; + + //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_HV_PALETTE = 20; + private static final int TRACK_VER_S_SLIDER = 21; + private static final int TRACK_HS_PALETTE = 30; + private static final int TRACK_VER_VALUE_SLIDER = 31; + private static final int TRACK_SV_PALETTE = 40; + private static final int TRACK_VER_H_SLIDER = 41; + private static final int TRACK_UV_PALETTE = 50; + private static final int TRACK_VER_Y_SLIDER = 51; + private static final int TRACK_R_SLIDER = 60; + private static final int TRACK_G_SLIDER = 61; + private static final int TRACK_B_SLIDER = 62; + private static final int TRACK_H_SLIDER = 70; + private static final int TRACK_S_SLIDER = 71; + private static final int TRACK_HOR_VALUE_SLIDER = 72; + private static final int TRACK_HOR_Y_SLIDER = 80; + private static final int TRACK_U_SLIDER = 81; + private static final int TRACK_V_SLIDER = 82; + + private static final int TEXT_SIZE = 12; + private static final int TEXT_HALF_SIZE = TEXT_SIZE / 2; //Can be used to vertically center text (sorta, it's approximate) + 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; + + private Shader mFadeInLeft, mFadeInTop, mFadeInRight, mFadeInBottom; + + //NEW_METHOD_WORK_NEEDED_HERE + //Add Paints to represent the palettes of the new method's UI controllers + private Paint mOvalHueSat; + + private Paint mOvalHueVal; + + private Shader mSatValMask; + private Paint mSatValPalette; + + private Paint mUVPalette; + + private Bitmap mVerSliderBM; + private Canvas mVerSliderCv; + + private Bitmap[] mHorSlidersBM = new Bitmap[3]; + private Canvas[] mHorSlidersCv = new Canvas[3]; + + private Paint mSatFader; + private Paint mValDimmer; + + //NEW_METHOD_WORK_NEEDED_HERE + //Add Paints to represent the icon for the new method + private Paint mOvalHueSatSmall; + private Paint mOvalHueValSmall; + private Paint mSVSmall; + private Paint mUVSmall; + private Paint[] mRGBSmall = new Paint[3]; + private Paint[] mHSSmall = new Paint[2]; + private Paint[] mYUVSmall = new Paint[3]; + + 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 Rect[] mHorSliderRects = new Rect[3]; + private Rect[] mMethodSelectorRects = null; //The Rects where the icons are drawn. This will be assigned during setup. + private int[] mMethodSelectRectMap = null; //Which method corresponds to which icon Rect. This will be assigned during setup. + + private int[] mSpectrumColors, 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; + private long mTimeOfLastSliderSwitch = 0; //To prevent slider switches from occurring too rapidly. + private boolean mShownYUVWarnedAlready = false; //Only show the YUV toast warning once. + + /** + * 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); + boolean focusable = isFocusable(); + boolean gotFocus = requestFocus(); + + mListener = l; + + mOriginalColor = color; + + Color.colorToHSV(color, mHSV); + if (isGray(color)) //Ugh, I think there's a bug in android's Color routines. Read the longer description at isGray(). + mHSV[1] = 0; + + updateAllFromHSV(); + + //Gather the number of enabled methods and allocate Rects to represent their icon locations in the method selector list. + NUM_ENABLED_METHODS = 0; + for (int i = 0; i < ENABLED_METHODS.length; i++) + if (ENABLED_METHODS[i]) + NUM_ENABLED_METHODS++; + if (NUM_ENABLED_METHODS == 0) { + Toast.makeText(getContext(), "No color picker methods enabled.", Toast.LENGTH_SHORT).show(); + throw new Exception("At least one method must be enabled"); + } + mMethodSelectorRects = new Rect[NUM_ENABLED_METHODS]; + mMethodSelectRectMap = new int[NUM_ENABLED_METHODS]; + + //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; + + METHOD_SELECTOR_POS_X = PALETTE_POS_X + PALETTE_DIM + SLIDER_THICKNESS + METHOD_SELECTOR_SPACING; + + //NEW_METHOD_WORK_NEEDED_HERE + //Follow the pattern here + mHSVenabled = ENABLED_METHODS[METHOD_HS_V_PALETTE] || ENABLED_METHODS[METHOD_HV_S_PALETTE] || ENABLED_METHODS[METHOD_SV_H_PALETTE] || ENABLED_METHODS[METHOD_HSV_SLIDERS]; + mRGBenabled = ENABLED_METHODS[METHOD_RGB_SLIDERS]; + mYUVenabled = ENABLED_METHODS[METHOD_UV_Y_PALETTE] || ENABLED_METHODS[METHOD_YUV_SLIDERS]; + mHexenabled = ENABLED_METHODS[METHOD_RGB_SLIDERS]; + + //Set the method chooser icon rects + int prevEnabledMethod = -1; + for (int i = 0; i < NUM_ENABLED_METHODS; i++) { + mMethodSelectorRects[i] = new Rect( + METHOD_SELECTOR_POS_X, + (METHOD_SELECTOR_SIZE + METHOD_SELECTOR_SPACING) * i, + METHOD_SELECTOR_POS_X + METHOD_SELECTOR_SIZE, + (METHOD_SELECTOR_SIZE + METHOD_SELECTOR_SPACING) * i + METHOD_SELECTOR_SIZE); + + for (int j = prevEnabledMethod + 1; j < ENABLED_METHODS.length; j++) { + if (ENABLED_METHODS[j]) { + mMethodSelectRectMap[i] = j; + prevEnabledMethod = j; + break; + } + } + } + + //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); + mHorSliderRects[0] = new Rect( + 0, + PALETTE_POS_Y + FIRST_HOR_SLIDER_POS_Y, + PALETTE_DIM, + PALETTE_POS_Y + FIRST_HOR_SLIDER_POS_Y + SLIDER_THICKNESS); + mHorSliderRects[1] = new Rect( + 0, + PALETTE_POS_Y + FIRST_HOR_SLIDER_POS_Y + (int)(SLIDER_THICKNESS * 1.25), + PALETTE_DIM, + PALETTE_POS_Y + FIRST_HOR_SLIDER_POS_Y + (int)(SLIDER_THICKNESS * 1.25) + SLIDER_THICKNESS); + mHorSliderRects[2] = new Rect( + 0, + PALETTE_POS_Y + FIRST_HOR_SLIDER_POS_Y + (int)(SLIDER_THICKNESS * 2.5), + PALETTE_DIM, + PALETTE_POS_Y + FIRST_HOR_SLIDER_POS_Y + (int)(SLIDER_THICKNESS * 2.5) + SLIDER_THICKNESS); + + 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 + METHOD_SELECTOR_SPACING + METHOD_SELECTOR_SIZE; + VIEW_DIM_Y = Math.max(SWATCH_HEIGHT + PALETTE_DIM + TEXT_SIZE * 4, + METHOD_SELECTOR_SIZE * NUM_ENABLED_METHODS + METHOD_SELECTOR_SPACING * (NUM_ENABLED_METHODS - 1)); + } + else { //Landscape layout + SWATCH_WIDTH = 110; + + PALETTE_POS_X = SWATCH_WIDTH; + PALETTE_POS_Y = 0; + + METHOD_SELECTOR_POS_X = PALETTE_POS_X + PALETTE_DIM + SLIDER_THICKNESS + METHOD_SELECTOR_SPACING; + + //NEW_METHOD_WORK_NEEDED_HERE + //Follow the pattern here + mHSVenabled = ENABLED_METHODS[METHOD_HS_V_PALETTE] || ENABLED_METHODS[METHOD_HV_S_PALETTE] || ENABLED_METHODS[METHOD_SV_H_PALETTE] || ENABLED_METHODS[METHOD_HSV_SLIDERS]; + mRGBenabled = ENABLED_METHODS[METHOD_RGB_SLIDERS]; + mYUVenabled = ENABLED_METHODS[METHOD_UV_Y_PALETTE] || ENABLED_METHODS[METHOD_YUV_SLIDERS]; + mHexenabled = ENABLED_METHODS[METHOD_RGB_SLIDERS]; + + //The maximum number of method selector icons per column is hard-coded to 4. + //Changing this parameter would require some care, especially in calculation of VIEW_DIM_Y. + + //Set the method chooser icon rects + int prevEnabledMethod = -1; + for (int i = 0; i < NUM_ENABLED_METHODS; i++) { + int xOffset = (METHOD_SELECTOR_SIZE + METHOD_SELECTOR_SPACING) * (i / 4); + mMethodSelectorRects[i] = new Rect( + METHOD_SELECTOR_POS_X + xOffset, + (METHOD_SELECTOR_SIZE + METHOD_SELECTOR_SPACING) * (i % 4), + METHOD_SELECTOR_POS_X + xOffset + METHOD_SELECTOR_SIZE, + (METHOD_SELECTOR_SIZE + METHOD_SELECTOR_SPACING) * (i % 4) + METHOD_SELECTOR_SIZE); + + for (int j = prevEnabledMethod + 1; j < ENABLED_METHODS.length; j++) { + if (ENABLED_METHODS[j]) { + mMethodSelectRectMap[i] = j; + prevEnabledMethod = j; + break; + } + } + } + int numMethodSelectorColumns = (int)Math.ceil(NUM_ENABLED_METHODS / 4.0f); + + //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); + mHorSliderRects[0] = new Rect( + SWATCH_WIDTH, + FIRST_HOR_SLIDER_POS_Y, + SWATCH_WIDTH + PALETTE_DIM, + FIRST_HOR_SLIDER_POS_Y + SLIDER_THICKNESS); + mHorSliderRects[1] = new Rect( + SWATCH_WIDTH, + FIRST_HOR_SLIDER_POS_Y + (int)(SLIDER_THICKNESS * 1.25), + SWATCH_WIDTH + PALETTE_DIM, + FIRST_HOR_SLIDER_POS_Y + (int)(SLIDER_THICKNESS * 1.25) + SLIDER_THICKNESS); + mHorSliderRects[2] = new Rect( + SWATCH_WIDTH, + FIRST_HOR_SLIDER_POS_Y + (int)(SLIDER_THICKNESS * 2.5), + SWATCH_WIDTH + PALETTE_DIM, + FIRST_HOR_SLIDER_POS_Y + (int)(SLIDER_THICKNESS * 2.5) + SLIDER_THICKNESS); + + 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 + + (METHOD_SELECTOR_SPACING + METHOD_SELECTOR_SIZE) * numMethodSelectorColumns; + VIEW_DIM_Y = Math.max(mNewSwatchRect.bottom, Math.max(PALETTE_DIM, METHOD_SELECTOR_SIZE * 4 + METHOD_SELECTOR_SPACING * 3)); + } + + //Rainbows make everybody happy! + mSpectrumColors = new int[] { + 0xFFFF0000, 0xFFFFFF00, 0xFF00FF00, 0xFF00FFFF, + 0xFF0000FF, 0xFFFF00FF, 0xFFFF0000, + }; + 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)); + + mFadeInLeft = new LinearGradient(0, 0, PALETTE_DIM, 0, 0xFF000000, 0x00000000, Shader.TileMode.CLAMP); + mFadeInRight = new LinearGradient(0, 0, PALETTE_DIM, 0, 0x00000000, 0xFF000000, Shader.TileMode.CLAMP); + mFadeInTop = new LinearGradient(0, 0, 0, PALETTE_DIM, 0xFF000000, 0x00000000, Shader.TileMode.CLAMP); + mFadeInBottom = new LinearGradient(0, 0, 0, PALETTE_DIM, 0x00000000, 0xFF000000, Shader.TileMode.CLAMP); + Shader fadeInTopSmall = new LinearGradient(0, 0, 0, METHOD_SELECTOR_SIZE, 0xFF000000, 0x00000000, Shader.TileMode.CLAMP); + Shader fadeInBottomSmall = new LinearGradient(0, 0, 0, METHOD_SELECTOR_SIZE, 0x00000000, 0xFF000000, Shader.TileMode.CLAMP); + + Shader shader = new SweepGradient(0, 0, mSpectrumColorsRev, null); + + Shader shaderA = new SweepGradient(0, 0, mSpectrumColorsRev, null); + Shader shaderB = new RadialGradient(0, 0, PALETTE_CENTER_X, 0xFFFFFFFF, 0xFF000000, Shader.TileMode.CLAMP); + 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); + + shaderB = new RadialGradient(0, 0, PALETTE_CENTER_X, 0xFF000000, 0xFFFFFFFF, Shader.TileMode.CLAMP); + shader = new ComposeShader(shaderA, shaderB, PorterDuff.Mode.MULTIPLY); + mOvalHueVal = new Paint(Paint.ANTI_ALIAS_FLAG); + mOvalHueVal.setShader(shader); + mOvalHueVal.setStyle(Paint.Style.FILL); + mOvalHueVal.setDither(true); + + shaderA = new LinearGradient(0, 0, 0, PALETTE_DIM, 0xFF000000, 0xFFFFFFFF, Shader.TileMode.CLAMP); + mSatValMask = new ComposeShader(shaderA, mFadeInRight, PorterDuff.Mode.DST_IN); //DST_ATOP also works + + mSatValPalette = new Paint(Paint.ANTI_ALIAS_FLAG); + mSatValPalette.setStyle(Paint.Style.FILL); + mSatValPalette.setDither(true); + + mUVPalette = new Paint(Paint.ANTI_ALIAS_FLAG); + mUVPalette.setStyle(Paint.Style.FILL); + mUVPalette.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]); + } + + mSatFader = new Paint(Paint.ANTI_ALIAS_FLAG); + mSatFader.setStyle(Paint.Style.FILL); + mSatFader.setDither(true); + mSatFader.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SCREEN)); + + 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, METHOD_SELECTOR_SIZE / 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); + + shaderB = new RadialGradient(0, 0, METHOD_SELECTOR_SIZE / 2, 0xFF000000, 0xFFFFFFFF, Shader.TileMode.CLAMP); + shader = new ComposeShader(shaderA, shaderB, PorterDuff.Mode.MULTIPLY); + mOvalHueValSmall = new Paint(Paint.ANTI_ALIAS_FLAG); + mOvalHueValSmall.setShader(shader); + mOvalHueValSmall.setStyle(Paint.Style.FILL); + + shaderA = new LinearGradient(0, 0, METHOD_SELECTOR_SIZE, 0, 0xFF000000, 0xFFFF0000, Shader.TileMode.CLAMP); + shaderB = new LinearGradient(0, 0, 0, METHOD_SELECTOR_SIZE, 0xFF000000, 0xFFFFFFFF, Shader.TileMode.CLAMP); + Shader shaderC = new LinearGradient(0, 0, METHOD_SELECTOR_SIZE, 0, 0x00000000, 0xFF000000, Shader.TileMode.CLAMP); + Shader shaderD = new ComposeShader(shaderB, shaderC, PorterDuff.Mode.DST_IN); + shader = new ComposeShader(shaderA, shaderD, PorterDuff.Mode.SCREEN); + + mSVSmall = new Paint(Paint.ANTI_ALIAS_FLAG); + mSVSmall.setShader(shader); + mSVSmall.setStyle(Paint.Style.FILL); + + //A UV palette (U across, V up) for a given Y value is estimated by painting a U gradient across + //the top for maximum V, faded out at the bottom, and painting a U gradient across the bottom for + //minimum V, faded out at the top, then blending them. This is pretty accurate, except for the + //center of the palette for extreme values of Y (very low or very high), in which the true darkness + //or lightness is not properly represented. + ColorMatrix cm = new ColorMatrix(); + cm.setYUV2RGB(); + final float[] a = cm.getArray(); + + int col1, col2; + float[] yuv = new float[3]; + int[] rgb = new int[3]; + + yuv[0] = .5f; + + //Top U, faded out at bottom + yuv[1] = -.5f; + yuv[2] = .5f; + matrixProductToByte(a, yuv, rgb); + col1 = Color.rgb(rgb[0], rgb[1], rgb[2]); + + yuv[1] = .5f; + matrixProductToByte(a, yuv, rgb); + col2 = Color.rgb(rgb[0], rgb[1], rgb[2]); + + shaderA = new LinearGradient(0, 0, METHOD_SELECTOR_SIZE, 0, col1, col2, Shader.TileMode.CLAMP); + Shader shaderA2 = new ComposeShader(shaderA, fadeInTopSmall, PorterDuff.Mode.DST_IN); + + //Bottom U, faded out at top + yuv[1] = -.5f; + yuv[2] = -.5f; + matrixProductToByte(a, yuv, rgb); + col1 = Color.rgb(rgb[0], rgb[1], rgb[2]); + + yuv[1] = .5f; + matrixProductToByte(a, yuv, rgb); + col2 = Color.rgb(rgb[0], rgb[1], rgb[2]); + + shaderB = new LinearGradient(0, 0, METHOD_SELECTOR_SIZE, 0, col1, col2, Shader.TileMode.CLAMP); + Shader shaderB2 = new ComposeShader(shaderB, fadeInBottomSmall, PorterDuff.Mode.DST_IN); + + shader = new ComposeShader(shaderA2, shaderB2, PorterDuff.Mode.SCREEN); + + mUVSmall = new Paint(Paint.ANTI_ALIAS_FLAG); + mUVSmall.setShader(shader); + mUVSmall.setStyle(Paint.Style.FILL); + + shader = new LinearGradient(0, 0, METHOD_SELECTOR_SIZE, 0, 0xFF000000, 0xFFFF0000, Shader.TileMode.CLAMP); + mRGBSmall[0] = new Paint(Paint.ANTI_ALIAS_FLAG); + mRGBSmall[0].setShader(shader); + mRGBSmall[0].setStyle(Paint.Style.FILL); + + shader = new LinearGradient(0, 0, METHOD_SELECTOR_SIZE, 0, 0xFF000000, 0xFF00FF00, Shader.TileMode.CLAMP); + mRGBSmall[1] = new Paint(Paint.ANTI_ALIAS_FLAG); + mRGBSmall[1].setShader(shader); + mRGBSmall[1].setStyle(Paint.Style.FILL); + + shader = new LinearGradient(0, 0, METHOD_SELECTOR_SIZE, 0, 0xFF000000, 0xFF0000FF, Shader.TileMode.CLAMP); + mRGBSmall[2] = new Paint(Paint.ANTI_ALIAS_FLAG); + mRGBSmall[2].setShader(shader); + mRGBSmall[2].setStyle(Paint.Style.FILL); + + shader = new LinearGradient(0, 0, METHOD_SELECTOR_SIZE, 0, mSpectrumColors, null, Shader.TileMode.CLAMP); + mHSSmall[0] = new Paint(Paint.ANTI_ALIAS_FLAG); + mHSSmall[0].setShader(shader); + mHSSmall[0].setStyle(Paint.Style.FILL); + + shader = new LinearGradient(0, 0, METHOD_SELECTOR_SIZE, 0, 0xFFFFFFFF, 0xFFFF0000, Shader.TileMode.CLAMP); + mHSSmall[1] = new Paint(Paint.ANTI_ALIAS_FLAG); + mHSSmall[1].setShader(shader); + mHSSmall[1].setStyle(Paint.Style.FILL); + + yuv[0] = 0; + yuv[1] = 0; + yuv[2] = 0; + matrixProductToByte(a, yuv, rgb); + col1 = Color.rgb(rgb[0], rgb[1], rgb[2]); + + yuv[0] = 1; + matrixProductToByte(a, yuv, rgb); + col2 = Color.rgb(rgb[0], rgb[1], rgb[2]); + + shader = new LinearGradient(0, 0, METHOD_SELECTOR_SIZE, 0, col1, col2, Shader.TileMode.CLAMP); + mYUVSmall[0] = new Paint(Paint.ANTI_ALIAS_FLAG); + mYUVSmall[0].setShader(shader); + mYUVSmall[0].setStyle(Paint.Style.FILL); + + yuv[0] = .5f; + yuv[1] = -.5f; + yuv[2] = 0; + matrixProductToByte(a, yuv, rgb); + col1 = Color.rgb(rgb[0], rgb[1], rgb[2]); + + yuv[1] = .5f; + matrixProductToByte(a, yuv, rgb); + col2 = Color.rgb(rgb[0], rgb[1], rgb[2]); + + shader = new LinearGradient(0, 0, METHOD_SELECTOR_SIZE, 0, col1, col2, Shader.TileMode.CLAMP); + mYUVSmall[1] = new Paint(Paint.ANTI_ALIAS_FLAG); + mYUVSmall[1].setShader(shader); + mYUVSmall[1].setStyle(Paint.Style.FILL); + + yuv[0] = .5f; + yuv[1] = 0; + yuv[2] = -.5f; + matrixProductToByte(a, yuv, rgb); + col1 = Color.rgb(rgb[0], rgb[1], rgb[2]); + + yuv[2] = .5f; + matrixProductToByte(a, yuv, rgb); + col2 = Color.rgb(rgb[0], rgb[1], rgb[2]); + + shader = new LinearGradient(0, 0, METHOD_SELECTOR_SIZE, 0, col1, col2, Shader.TileMode.CLAMP); + mYUVSmall[2] = new Paint(Paint.ANTI_ALIAS_FLAG); + mYUVSmall[2].setShader(shader); + mYUVSmall[2].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) + //NEW_METHOD_WORK_NEEDED_HERE + //To add a new method, replicate and extend the last entry in this list + if (mMethod == METHOD_HS_V_PALETTE) + drawHSV1Palette(canvas); + else if (mMethod == METHOD_HV_S_PALETTE) + drawHSV2Palette(canvas); + else if (mMethod == METHOD_SV_H_PALETTE) + drawHSV3Palette(canvas); + else if (mMethod == METHOD_UV_Y_PALETTE) + drawYUVPalette(canvas); + else if (mMethod == METHOD_RGB_SLIDERS) + drawRGBSliders(canvas); + else if (mMethod == METHOD_HSV_SLIDERS) + drawHSVSliders(canvas); + else if (mMethod == METHOD_YUV_SLIDERS) + drawYUVSliders(canvas); + + //Draw the method selector icons + drawMethodSelectors(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); + } + + /** + * Draw a line across the slider to indicate its current value. + * @param canvas + * @param markerPos + */ + private void markHorSlider(Canvas canvas, int markerPos) { + mPosMarker.setColor(Color.BLACK); + canvas.drawRect(new Rect(markerPos - 2, 0, markerPos + 3, SLIDER_THICKNESS), mPosMarker); + mPosMarker.setColor(Color.WHITE); + canvas.drawRect(new Rect(markerPos, 0, markerPos + 1, SLIDER_THICKNESS), 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 slider to indicate that it has trackball focus. + * @param canvas + */ + private void hilightFocusedHorSlider(Canvas canvas) { + mPosMarker.setColor(Color.WHITE); + canvas.drawRect(new Rect(0, 0, PALETTE_DIM, SLIDER_THICKNESS), mPosMarker); + mPosMarker.setColor(Color.BLACK); + canvas.drawRect(new Rect(2, 2, PALETTE_DIM - 2, SLIDER_THICKNESS - 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); + } + + /** + * Frame the 2D palette to indicate that it has trackball focus. + * @param canvas + */ + private void hilightFocusedSquarePalette(Canvas canvas) { + mPosMarker.setColor(Color.WHITE); + canvas.drawRect(new Rect(0, 0, PALETTE_DIM, PALETTE_DIM), mPosMarker); + mPosMarker.setColor(Color.BLACK); + canvas.drawRect(new Rect(2, 2, PALETTE_DIM - 2, PALETTE_DIM - 2), mPosMarker); + } + + /** + * Frame the 2D palette to indicate that it has trackball focus. + * @param canvas + */ + private void hilightMethodSelectorOval(Canvas canvas) { + mPosMarker.setColor(Color.WHITE); + canvas.drawOval(new RectF(-METHOD_SELECTOR_SIZE / 2, -METHOD_SELECTOR_SIZE / 2, METHOD_SELECTOR_SIZE / 2, METHOD_SELECTOR_SIZE / 2), mPosMarker); + mPosMarker.setColor(Color.BLACK); + canvas.drawOval(new RectF(-METHOD_SELECTOR_SIZE / 2 + 2, -METHOD_SELECTOR_SIZE / 2 + 2, METHOD_SELECTOR_SIZE / 2 - 2, METHOD_SELECTOR_SIZE / 2 - 2), mPosMarker); + } + + /** + * Frame the 2D palette to indicate that it has trackball focus. + * @param canvas + */ + private void hilightMethodSelectorRect(Canvas canvas) { + mPosMarker.setColor(Color.WHITE); + canvas.drawRect(new Rect(0, 0, METHOD_SELECTOR_SIZE, METHOD_SELECTOR_SIZE), mPosMarker); + mPosMarker.setColor(Color.BLACK); + canvas.drawRect(new Rect(2, 2, METHOD_SELECTOR_SIZE - 2, METHOD_SELECTOR_SIZE - 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(); + } + + /** + * Draw the UI for HSV with angular H and radial V combined in 2D and a 1D S slider. + * @param canvas + */ + private void drawHSV2Palette(Canvas canvas) { + canvas.save(); + + canvas.translate(PALETTE_POS_X, PALETTE_POS_Y); + + canvas.translate(PALETTE_CENTER_X, PALETTE_CENTER_Y); + canvas.drawOval(new RectF(-PALETTE_RADIUS, -PALETTE_RADIUS, PALETTE_RADIUS, PALETTE_RADIUS), mOvalHueVal); + canvas.drawOval(new RectF(-PALETTE_RADIUS, -PALETTE_RADIUS, PALETTE_RADIUS, PALETTE_RADIUS), mSatFader); + if (mFocusedControl == 0) + hilightFocusedOvalPalette(canvas); + mark2DPalette(canvas, mCoord[0], mCoord[2]); + canvas.translate(-PALETTE_CENTER_X, -PALETTE_CENTER_Y); + + canvas.translate(PALETTE_DIM, 0); + canvas.drawBitmap(mVerSliderBM, 0, 0, null); + if (mFocusedControl == 1) + hilightFocusedVerSlider(canvas); + if (mFocusedControl == 1) + hilightFocusedVerSlider(canvas); + markVerSlider(canvas, mCoord[1]); + + canvas.restore(); + } + + /** + * Draw the UI for HSV with cardinal S and V combined in 2D and a 1D H slider. + * @param canvas + */ + private void drawHSV3Palette(Canvas canvas) { + canvas.save(); + + canvas.translate(PALETTE_POS_X, PALETTE_POS_Y); + + canvas.drawRect(new Rect(0, 0, PALETTE_DIM, PALETTE_DIM), mSatValPalette); + if (mFocusedControl == 0) + hilightFocusedSquarePalette(canvas); + mark2DPalette(canvas, mCoord[2], mCoord[1]); + + canvas.translate(PALETTE_DIM, 0); + canvas.drawBitmap(mVerSliderBM, 0, 0, null); + if (mFocusedControl == 1) + hilightFocusedVerSlider(canvas); + canvas.drawRect(new Rect(0, 0, SLIDER_THICKNESS, PALETTE_DIM), mSatFader); + canvas.drawRect(new Rect(0, 0, SLIDER_THICKNESS, PALETTE_DIM), mValDimmer); + if (mFocusedControl == 1) + hilightFocusedVerSlider(canvas); + markVerSlider(canvas, mCoord[0]); + + canvas.restore(); + } + + /** + * Draw the UI for YUV with cardinal U and V combined in 2D and a 1D Y slider. + * @param canvas + */ + private void drawYUVPalette(Canvas canvas) { + canvas.save(); + + canvas.translate(PALETTE_POS_X, PALETTE_POS_Y); + + Paint black = new Paint(Paint.ANTI_ALIAS_FLAG); + black.setStyle(Paint.Style.FILL); + black.setColor(Color.BLACK); + canvas.drawRect(new Rect(0, 0, PALETTE_DIM, PALETTE_DIM), black); + canvas.drawRect(new Rect(0, 0, PALETTE_DIM, PALETTE_DIM), mUVPalette); + if (mFocusedControl == 0) + hilightFocusedSquarePalette(canvas); + mark2DPalette(canvas, mCoord[1], mCoord[2]); + + canvas.translate(PALETTE_DIM, 0); + canvas.drawBitmap(mVerSliderBM, 0, 0, null); + if (mFocusedControl == 1) + hilightFocusedVerSlider(canvas); + if (mFocusedControl == 1) + hilightFocusedVerSlider(canvas); + markVerSlider(canvas, mCoord[0]); + + canvas.restore(); + } + + /** + * Draw the UI for RGB with three 1D sliders. + * @param canvas + */ + private void drawRGBSliders(Canvas canvas) { + canvas.save(); + + canvas.translate(PALETTE_POS_X, PALETTE_POS_Y); + + for (int i = 0; i < 3; i++) { + if (i == 0) + canvas.translate(0, FIRST_HOR_SLIDER_POS_Y); + else canvas.translate(0, (int)(SLIDER_THICKNESS * 1.25)); + + canvas.drawBitmap(mHorSlidersBM[i], 0, 0, null); + + if (mFocusedControl == i) + hilightFocusedHorSlider(canvas); + markHorSlider(canvas, mCoord[i]); + + if (i == 0) + canvas.drawText("R", PALETTE_DIM + 5, SLIDER_THICKNESS / 2 + TEXT_HALF_SIZE, mText); + else if (i == 1) + canvas.drawText("G", PALETTE_DIM + 5, SLIDER_THICKNESS / 2 + TEXT_HALF_SIZE, mText); + else canvas.drawText("B", PALETTE_DIM + 5, SLIDER_THICKNESS / 2 + TEXT_HALF_SIZE, mText); + } + + canvas.restore(); + } + + /** + * Draw the UI for HSV with three 1D sliders. + * @param canvas + */ + private void drawHSVSliders(Canvas canvas) { + canvas.save(); + + canvas.translate(PALETTE_POS_X, PALETTE_POS_Y); + + for (int i = 0; i < 3; i++) { + if (i == 0) + canvas.translate(0, FIRST_HOR_SLIDER_POS_Y); + else canvas.translate(0, (int)(SLIDER_THICKNESS * 1.25)); + + canvas.drawBitmap(mHorSlidersBM[i], 0, 0, null); + + if (i == 0) { + canvas.drawRect(new Rect(0, 0, PALETTE_DIM, SLIDER_THICKNESS), mSatFader); + canvas.drawRect(new Rect(0, 0, PALETTE_DIM, SLIDER_THICKNESS), mValDimmer); + } + + if (mFocusedControl == i) + hilightFocusedHorSlider(canvas); + markHorSlider(canvas, mCoord[i]); + + if (i == 0) + canvas.drawText("H", PALETTE_DIM + 5, SLIDER_THICKNESS / 2 + TEXT_HALF_SIZE, mText); + else if (i == 1) + canvas.drawText("S", PALETTE_DIM + 5, SLIDER_THICKNESS / 2 + TEXT_HALF_SIZE, mText); + else canvas.drawText("V", PALETTE_DIM + 5, SLIDER_THICKNESS / 2 + TEXT_HALF_SIZE, mText); + } + + canvas.restore(); + } + + /** + * Draw the UI for RGB with three 1D sliders. + * @param canvas + */ + private void drawYUVSliders(Canvas canvas) { + canvas.save(); + + canvas.translate(PALETTE_POS_X, PALETTE_POS_Y); + + for (int i = 0; i < 3; i++) { + if (i == 0) + canvas.translate(0, FIRST_HOR_SLIDER_POS_Y); + else canvas.translate(0, (int)(SLIDER_THICKNESS * 1.25)); + + canvas.drawBitmap(mHorSlidersBM[i], 0, 0, null); + + if (mFocusedControl == i) + hilightFocusedHorSlider(canvas); + markHorSlider(canvas, mCoord[i]); + + if (i == 0) + canvas.drawText("Y", PALETTE_DIM + 5, SLIDER_THICKNESS / 2 + TEXT_HALF_SIZE, mText); + else if (i == 1) + canvas.drawText("U", PALETTE_DIM + 5, SLIDER_THICKNESS / 2 + TEXT_HALF_SIZE, mText); + else canvas.drawText("V", PALETTE_DIM + 5, SLIDER_THICKNESS / 2 + TEXT_HALF_SIZE, mText); + } + + canvas.restore(); + } + + /** + * Draw the method selector icons + * @param canvas + */ + private void drawMethodSelectors(Canvas canvas) { + for (int i = 0; i < NUM_ENABLED_METHODS; i++) { + canvas.save(); + + //NEW_METHOD_WORK_NEEDED_HERE + //To add a new method, replicate and extend the last entry in this list + switch (mMethodSelectRectMap[i]) { + case METHOD_HS_V_PALETTE: { + canvas.translate(mMethodSelectorRects[i].left + METHOD_SELECTOR_SIZE / 2, mMethodSelectorRects[i].top + METHOD_SELECTOR_SIZE / 2); + canvas.drawOval(new RectF(-METHOD_SELECTOR_SIZE / 2, -METHOD_SELECTOR_SIZE / 2, METHOD_SELECTOR_SIZE / 2, METHOD_SELECTOR_SIZE / 2), mOvalHueSatSmall); + + if (mMethod == i) + hilightMethodSelectorOval(canvas); + } + break; + case METHOD_HV_S_PALETTE: { + canvas.translate(mMethodSelectorRects[i].left + METHOD_SELECTOR_SIZE / 2, mMethodSelectorRects[i].top + METHOD_SELECTOR_SIZE / 2); + canvas.drawOval(new RectF(-METHOD_SELECTOR_SIZE / 2, -METHOD_SELECTOR_SIZE / 2, METHOD_SELECTOR_SIZE / 2, METHOD_SELECTOR_SIZE / 2), mOvalHueValSmall); + + if (mMethod == i) + hilightMethodSelectorOval(canvas); + } + break; + case METHOD_SV_H_PALETTE: { + canvas.translate(mMethodSelectorRects[i].left, mMethodSelectorRects[i].top); + canvas.drawRect(new RectF(0, 0, METHOD_SELECTOR_SIZE, METHOD_SELECTOR_SIZE), mSVSmall); + + if (mMethod == i) + hilightMethodSelectorRect(canvas); + } + break; + case METHOD_UV_Y_PALETTE: { + canvas.translate(mMethodSelectorRects[i].left, mMethodSelectorRects[i].top); + canvas.drawRect(new RectF(0, 0, METHOD_SELECTOR_SIZE, METHOD_SELECTOR_SIZE), mUVSmall); + + if (mMethod == i) + hilightMethodSelectorRect(canvas); + } + break; + case METHOD_RGB_SLIDERS: { + canvas.translate(mMethodSelectorRects[i].left, mMethodSelectorRects[i].top + METHOD_SELECTOR_SIZE / 16); + canvas.drawRect(new RectF(0, 0, METHOD_SELECTOR_SIZE, METHOD_SELECTOR_SIZE / 4), mRGBSmall[0]); + canvas.translate(0, METHOD_SELECTOR_SIZE / 3); + canvas.drawRect(new RectF(0, 0, METHOD_SELECTOR_SIZE, METHOD_SELECTOR_SIZE / 4), mRGBSmall[1]); + canvas.translate(0, METHOD_SELECTOR_SIZE / 3); + canvas.drawRect(new RectF(0, 0, METHOD_SELECTOR_SIZE, METHOD_SELECTOR_SIZE / 4), mRGBSmall[2]); + + canvas.translate(0, -(2 * (METHOD_SELECTOR_SIZE / 3) + (METHOD_SELECTOR_SIZE / 16))); + if (mMethod == i) + hilightMethodSelectorRect(canvas); + } + break; + case METHOD_HSV_SLIDERS: { + canvas.translate(mMethodSelectorRects[i].left, mMethodSelectorRects[i].top + METHOD_SELECTOR_SIZE / 16); + canvas.drawRect(new RectF(0, 0, METHOD_SELECTOR_SIZE, METHOD_SELECTOR_SIZE / 4), mHSSmall[0]); + canvas.translate(0, METHOD_SELECTOR_SIZE / 3); + canvas.drawRect(new RectF(0, 0, METHOD_SELECTOR_SIZE, METHOD_SELECTOR_SIZE / 4), mHSSmall[1]); + canvas.translate(0, METHOD_SELECTOR_SIZE / 3); + canvas.drawRect(new RectF(0, 0, METHOD_SELECTOR_SIZE, METHOD_SELECTOR_SIZE / 4), mRGBSmall[0]); + + canvas.translate(0, -(2 * (METHOD_SELECTOR_SIZE / 3) + (METHOD_SELECTOR_SIZE / 16))); + if (mMethod == i) + hilightMethodSelectorRect(canvas); + } + break; + case METHOD_YUV_SLIDERS: { + canvas.translate(mMethodSelectorRects[i].left, mMethodSelectorRects[i].top + METHOD_SELECTOR_SIZE / 16); + canvas.drawRect(new RectF(0, 0, METHOD_SELECTOR_SIZE, METHOD_SELECTOR_SIZE / 4), mYUVSmall[0]); + canvas.translate(0, METHOD_SELECTOR_SIZE / 3); + canvas.drawRect(new RectF(0, 0, METHOD_SELECTOR_SIZE, METHOD_SELECTOR_SIZE / 4), mYUVSmall[1]); + canvas.translate(0, METHOD_SELECTOR_SIZE / 3); + canvas.drawRect(new RectF(0, 0, METHOD_SELECTOR_SIZE, METHOD_SELECTOR_SIZE / 4), mYUVSmall[2]); + + canvas.translate(0, -(2 * (METHOD_SELECTOR_SIZE / 3) + (METHOD_SELECTOR_SIZE / 16))); + if (mMethod == i) + hilightMethodSelectorRect(canvas); + } + break; + } + + canvas.restore(); + } + } + + /** + * Initialize the current color chooser's UI (set its color parameters and set its palette and slider values accordingly). + */ + private void initUI() { + //NEW_METHOD_WORK_NEEDED_HERE + //To add a new method, replicate and extend the last entry in this list + switch (mMethod) { + case METHOD_HS_V_PALETTE: + initHSV1Palette(); + break; + case METHOD_HV_S_PALETTE: + initHSV2Palette(); + break; + case METHOD_SV_H_PALETTE: + initHSV3Palette(); + break; + case METHOD_UV_Y_PALETTE: + initYUVPalette(); + break; + case METHOD_RGB_SLIDERS: + initRGBSliders(); + break; + case METHOD_HSV_SLIDERS: + initHSVSliders(); + break; + case METHOD_YUV_SLIDERS: + initYUVSliders(); + break; + } + + //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)(Math.cos(angle) * radius); + mCoord[1] = (int)(Math.sin(angle) * radius); + + mCoord[2] = PALETTE_DIM - (int)(mHSV[2] * PALETTE_DIM); + } + + /** + * Initialize a color chooser. + */ + private void initHSV2Palette() { + setOvalSatFader(); + setVerSatSlider(); + + float angle = 2*PI - mHSV[0] / (180 / 3.1415927f); + float radius = mHSV[1] * PALETTE_RADIUS; + mCoord[0] = (int)(Math.cos(angle) * radius); + mCoord[2] = (int)(Math.sin(angle) * radius); + + mCoord[1] = PALETTE_DIM - (int)(mHSV[1] * PALETTE_DIM); + } + + /** + * Initialize a color chooser. + */ + private void initHSV3Palette() { + Shader shader = new LinearGradient(0, PALETTE_DIM, 0, 0, mSpectrumColors, null, Shader.TileMode.CLAMP); + + GradientDrawable gradDraw = new GradientDrawable(Orientation.TOP_BOTTOM, mSpectrumColorsRev); + gradDraw.setDither(true); + gradDraw.setLevel(10000); + gradDraw.setBounds(0, 0, SLIDER_THICKNESS, PALETTE_DIM); + gradDraw.draw(mVerSliderCv); + + setHorSatFader(); + setHorValDimmer(); + setSatValPalette(); + setVerHueSlider(); + + mCoord[1] = PALETTE_DIM - (int)(mHSV[1] * PALETTE_DIM); + mCoord[2] = (int)(mHSV[2] * PALETTE_DIM); + + mCoord[0] = PALETTE_DIM - (int)((mHSV[0] / 360.0f) * PALETTE_DIM); + } + + /** + * Initialize a color chooser. + */ + private void initYUVPalette() { + int color = Color.HSVToColor(mHSV); + float r = Color.red(color) / 255.0f; + float g = Color.green(color) / 255.0f; + float b = Color.blue(color) / 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); + + setUVPalette(); + setVerYSlider(); + + mCoord[1] = (int)((mYUV[1] + .5f) * PALETTE_DIM); + mCoord[2] = PALETTE_DIM - (int)((mYUV[2] + .5f) * PALETTE_DIM); + + mCoord[0] = PALETTE_DIM - (int)(mYUV[0] * PALETTE_DIM); + + //Warn the user that the UV 2D palette is only an estimate, but that the swatch is correct. + if (!mShownYUVWarnedAlready) + Toast.makeText(getContext(), "Note that the UV 2D palette only shows an estimate " + + "but the swatch is correct.", Toast.LENGTH_LONG).show(); + mShownYUVWarnedAlready = true; + } + + /** + * Initialize a color chooser. + */ + private void initRGBSliders() { + int color = Color.HSVToColor(mHSV); + mRGB[0] = Color.red(color); + mRGB[1] = Color.green(color); + mRGB[2] = Color.blue(color); + + setHorRSlider(); + setHorGSlider(); + setHorBSlider(); + + int col = Color.HSVToColor(mHSV); + mCoord[0] = (int)(PALETTE_DIM * (Color.red(col) / 255.0f)); + mCoord[1] = (int)(PALETTE_DIM * (Color.green(col) / 255.0f)); + mCoord[2] = (int)(PALETTE_DIM * (Color.blue(col) / 255.0f)); + } + + /** + * Initialize a color chooser. + */ + private void initHSVSliders() { + Shader shader = new LinearGradient(0, 0, PALETTE_DIM, 0, mSpectrumColors, null, Shader.TileMode.CLAMP); + + GradientDrawable gradDraw = new GradientDrawable(Orientation.LEFT_RIGHT, mSpectrumColors); + gradDraw.setDither(true); + gradDraw.setLevel(10000); + gradDraw.setBounds(0, 0, PALETTE_DIM, SLIDER_THICKNESS); + gradDraw.draw(mHorSlidersCv[0]); + + setHorSatFader(); + setHorValDimmer(); + setHorSatSlider(); + setHorValSlider(); + + mCoord[0] = (int)((mHSV[0] / 360.0f) * PALETTE_DIM); + mCoord[1] = (int)(mHSV[1] * PALETTE_DIM); + mCoord[2] = (int)(mHSV[2] * PALETTE_DIM); + } + + /** + * Initialize a color chooser. + */ + private void initYUVSliders() { + int color = Color.HSVToColor(mHSV); + float r = Color.red(color) / 255.0f; + float g = Color.green(color) / 255.0f; + float b = Color.blue(color) / 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); + + setHorYSlider(); + setHorUSlider(); + setHorVSlider(); + + mCoord[0] = (int)(mYUV[0] * PALETTE_DIM); + mCoord[1] = (int)((mYUV[1] + .5f) * PALETTE_DIM); + mCoord[2] = (int)((mYUV[2] + .5f) * 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); + } + + /** + * Adjust a Paint which, when painted, fades its underlying object to show the effects of varying saturation. + */ + private void setOvalSatFader() { + float[] hsv = new float[3]; + hsv[0] = mHSV[0]; + hsv[1] = 0; + hsv[2] = (1 - mHSV[1]) * mHSV[2]; + int gray = Color.HSVToColor(hsv); + mSatFader.setColor(gray); + } + + /** + * Create a linear gradient shader to show variations in saturation. + */ + private void setVerSatSlider() { + float[] hsv = new float[3]; + hsv[0] = mHSV[0]; + hsv[1] = 1; + hsv[2] = mHSV[2]; + int col1 = Color.HSVToColor(hsv); + hsv[0] = mHSV[0]; + hsv[1] = 0; + hsv[2] = mHSV[2]; + int col2 = Color.HSVToColor(hsv); + + int colors[] = new int[2]; + colors[0] = col1; + colors[1] = col2; + 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); + } + + /** + * Create a cardinal 2D palette with increasing value to the right and increasing saturation upwards, for a given hue. + */ + private void setSatValPalette() { + float[] hsv = new float[3]; + hsv[0] = mHSV[0]; + hsv[1] = 1; + hsv[2] = 1; + int hue = Color.HSVToColor(hsv); + + Shader shaderA = new LinearGradient(0, 0, PALETTE_DIM, 0, 0xFF000000, hue, Shader.TileMode.CLAMP); + Shader shaderB = new ComposeShader(shaderA, mSatValMask, PorterDuff.Mode.SCREEN); + mSatValPalette.setShader(shaderB); + } + + /** + * Place holder to keep the "setSlider" pattern, but there's nothing to do here. + */ + private void setVerHueSlider() { + //Nothing to do + } + + /** + * Adjust a Paint which, when painted, fades its underlying object to show the effects of varying saturation. + */ + private void setHorSatFader() { + float[] hsv = new float[3]; + hsv[0] = mHSV[0]; + hsv[1] = 0; + hsv[2] = 1.0f - mHSV[1]; + int gray = Color.HSVToColor(hsv); + mSatFader.setColor(gray); + } + + /** + * Adjust a Paint which, when painted, dims its underlying object to show the effects of varying value (brightness). + */ + private void setHorValDimmer() { + 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 cardinal 2D palette (in YUV space) with increasing U to the right and increasing V upwards, for a given Y. + * <P> + * This UV palette (U across, V up) for a given Y value is estimated by painting a U gradient across + * the top for maximum V, alpha-faded out at the bottom, and painting a U gradient across the bottom for + * minimum V, alpha-faded out at the top, then blending them. This fairly accurately simulates the UV palette, + * except for the center of the palette for extreme values of Y (very low or very high), + * in which the true darkness or lightness is not properly represented. + */ + private void setUVPalette() { + ColorMatrix cm = new ColorMatrix(); + cm.setYUV2RGB(); + final float[] a = cm.getArray(); + + int col1, col2; + float[] yuv = new float[3]; + int[] rgb = new int[3]; + + yuv[0] = mYUV[0]; + + //Top U, alpha-faded out at bottom + yuv[1] = -.5f; + yuv[2] = .5f; + matrixProductToByte(a, yuv, rgb); + col1 = Color.rgb(rgb[0], rgb[1], rgb[2]); + + yuv[1] = .5f; + matrixProductToByte(a, yuv, rgb); + col2 = Color.rgb(rgb[0], rgb[1], rgb[2]); + + Shader shaderA = new LinearGradient(0, 0, PALETTE_DIM, 0, col1, col2, Shader.TileMode.CLAMP); + Shader shaderA2 = new ComposeShader(shaderA, mFadeInTop, PorterDuff.Mode.DST_IN); + + //Bottom U, alpha-faded out at top + yuv[1] = -.5f; + yuv[2] = -.5f; + matrixProductToByte(a, yuv, rgb); + col1 = Color.rgb(rgb[0], rgb[1], rgb[2]); + + yuv[1] = .5f; + matrixProductToByte(a, yuv, rgb); + col2 = Color.rgb(rgb[0], rgb[1], rgb[2]); + + Shader shaderB = new LinearGradient(0, 0, PALETTE_DIM, 0, col1, col2, Shader.TileMode.CLAMP); + Shader shaderB2 = new ComposeShader(shaderB, mFadeInBottom, PorterDuff.Mode.DST_IN); + + Shader shaderC = new ComposeShader(shaderA2, shaderB2, PorterDuff.Mode.SCREEN); + + //The center of the palette will be too saturated and not bright or dark enough at extreme Y values. + //Let's compensate a bit here. Yes, this is a hack. + Shader shaderD = null; + Shader shader = null; + if (mYUV[0] >= .5) { + int gray = pinToByte((int)((mYUV[0] - .5f) * 512)); + int trans = pinToByte((int)((mYUV[0] - .5f) * 480)); + col1 = Color.argb(trans, gray, gray, gray); + shaderD = new RadialGradient(PALETTE_CENTER_X, PALETTE_CENTER_Y, PALETTE_RADIUS, col1, 0x00000000, Shader.TileMode.CLAMP); + shader = new ComposeShader(shaderC, shaderD, PorterDuff.Mode.SCREEN); + } + else { + int gray = pinToByte((int)((mYUV[0] + .5f) * 512)); + int trans = pinToByte((int)((1.0f - (mYUV[0] + .5f)) * 448)); + col1 = Color.argb(trans, gray, gray, gray); + shaderD = new RadialGradient(PALETTE_CENTER_X, PALETTE_CENTER_Y, PALETTE_RADIUS, col1, 0x00000000, Shader.TileMode.CLAMP); + shader = new ComposeShader(shaderC, shaderD, PorterDuff.Mode.DST_OUT); + } + + mUVPalette.setShader(shader); + } + + /** + * Create a linear gradient shader to show variations in Y (in YUV). + */ + private void setVerYSlider() { + ColorMatrix cm = new ColorMatrix(); + cm.setYUV2RGB(); + final float[] a = cm.getArray(); + + float[] yuv = new float[3]; + int[] rgb = new int[3]; + + yuv[1] = mYUV[1]; + yuv[2] = mYUV[2]; + int colors[] = new int[11]; + for (int i = 0; i <= 10; i++) { + yuv[0] = i / 10.0f; + matrixProductToByte(a, yuv, rgb); + colors[10 - i] = Color.rgb(rgb[0], rgb[1], rgb[2]); + } + + 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); + } + + /** + * Create a linear gradient shader to show variations in red. + */ + private void setHorRSlider() { + int col1 = Color.rgb(0, mRGB[1], mRGB[2]); + int col2 = Color.rgb(255, mRGB[1], mRGB[2]); + + int colors[] = new int[2]; + colors[0] = col1; + colors[1] = col2; + GradientDrawable gradDraw = new GradientDrawable(Orientation.LEFT_RIGHT, colors); + gradDraw.setDither(true); + gradDraw.setLevel(10000); + gradDraw.setBounds(0, 0, PALETTE_DIM, SLIDER_THICKNESS); + gradDraw.draw(mHorSlidersCv[0]); + } + + /** + * Create a linear gradient shader to show variations in green. + */ + private void setHorGSlider() { + int col1 = Color.rgb(mRGB[0], 0, mRGB[2]); + int col2 = Color.rgb(mRGB[0], 255, mRGB[2]); + + int colors[] = new int[2]; + colors[0] = col1; + colors[1] = col2; + GradientDrawable gradDraw = new GradientDrawable(Orientation.LEFT_RIGHT, colors); + gradDraw.setDither(true); + gradDraw.setLevel(10000); + gradDraw.setBounds(0, 0, PALETTE_DIM, SLIDER_THICKNESS); + gradDraw.draw(mHorSlidersCv[1]); + } + + /** + * Create a linear gradient shader to show variations in blue. + */ + private void setHorBSlider() { + int col1 = Color.rgb(mRGB[0], mRGB[1], 0); + int col2 = Color.rgb(mRGB[0], mRGB[1], 255); + + int colors[] = new int[2]; + colors[0] = col1; + colors[1] = col2; + GradientDrawable gradDraw = new GradientDrawable(Orientation.LEFT_RIGHT, colors); + gradDraw.setDither(true); + gradDraw.setLevel(10000); + gradDraw.setBounds(0, 0, PALETTE_DIM, SLIDER_THICKNESS); + gradDraw.draw(mHorSlidersCv[2]); + } + + /** + * Create a linear gradient shader to show variations in saturation. + */ + private void setHorSatSlider() { + float[] hsv = new float[3]; + hsv[0] = mHSV[0]; + hsv[1] = 0; + hsv[2] = mHSV[2]; + int col1 = Color.HSVToColor(hsv); + hsv[0] = mHSV[0]; + hsv[1] = 1; + hsv[2] = mHSV[2]; + int col2 = Color.HSVToColor(hsv); + + int colors[] = new int[2]; + colors[0] = col1; + colors[1] = col2; + GradientDrawable gradDraw = new GradientDrawable(Orientation.LEFT_RIGHT, colors); + gradDraw.setDither(true); + gradDraw.setLevel(10000); + gradDraw.setBounds(0, 0, PALETTE_DIM, SLIDER_THICKNESS); + gradDraw.draw(mHorSlidersCv[1]); + } + + /** + * Create a linear gradient shader to show variations in value. + */ + private void setHorValSlider() { + 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] = 0xFF000000; + colors[1] = col; + GradientDrawable gradDraw = new GradientDrawable(Orientation.LEFT_RIGHT, colors); + gradDraw.setDither(true); + gradDraw.setLevel(10000); + gradDraw.setBounds(0, 0, PALETTE_DIM, SLIDER_THICKNESS); + gradDraw.draw(mHorSlidersCv[2]); + } + + /** + * Create a linear gradient shader to show variations in Y (in YUV). + */ + private void setHorYSlider() { + ColorMatrix cm = new ColorMatrix(); + cm.setYUV2RGB(); + final float[] a = cm.getArray(); + + float[] yuv = new float[3]; + int[] rgb = new int[3]; + + yuv[1] = mYUV[1]; + yuv[2] = mYUV[2]; + int colors[] = new int[11]; + for (int i = 0; i <= 10; i++) { + yuv[0] = i / 10.0f; + matrixProductToByte(a, yuv, rgb); + colors[i] = Color.rgb(rgb[0], rgb[1], rgb[2]); + } + + GradientDrawable gradDraw = new GradientDrawable(Orientation.LEFT_RIGHT, colors); + gradDraw.setDither(true); + gradDraw.setLevel(10000); + gradDraw.setBounds(0, 0, PALETTE_DIM, SLIDER_THICKNESS); + gradDraw.draw(mHorSlidersCv[0]); + } + + /** + * Create a linear gradient shader to show variations in U (in YUV). + */ + private void setHorUSlider() { + ColorMatrix cm = new ColorMatrix(); + cm.setYUV2RGB(); + final float[] a = cm.getArray(); + + float[] yuv = new float[3]; + int[] rgb = new int[3]; + + yuv[0] = mYUV[0]; + yuv[2] = mYUV[2]; + int colors[] = new int[11]; + for (int i = -5; i <= 5; i++) { + yuv[1] = i / 10.0f; + matrixProductToByte(a, yuv, rgb); + colors[i + 5] = Color.rgb(rgb[0], rgb[1], rgb[2]); + } + + GradientDrawable gradDraw = new GradientDrawable(Orientation.LEFT_RIGHT, colors); + gradDraw.setDither(true); + gradDraw.setLevel(10000); + gradDraw.setBounds(0, 0, PALETTE_DIM, SLIDER_THICKNESS); + gradDraw.draw(mHorSlidersCv[1]); + } + + /** + * Create a linear gradient shader to show variations in V (in YUV). + */ + private void setHorVSlider() { + ColorMatrix cm = new ColorMatrix(); + cm.setYUV2RGB(); + final float[] a = cm.getArray(); + + float[] yuv = new float[3]; + int[] rgb = new int[3]; + + yuv[0] = mYUV[0]; + yuv[1] = mYUV[1]; + int colors[] = new int[11]; + for (int i = -5; i <= 5; i++) { + yuv[2] = i / 10.0f; + matrixProductToByte(a, yuv, rgb); + colors[i + 5] = Color.rgb(rgb[0], rgb[1], rgb[2]); + } + + GradientDrawable gradDraw = new GradientDrawable(Orientation.LEFT_RIGHT, colors); + gradDraw.setDither(true); + gradDraw.setLevel(10000); + gradDraw.setBounds(0, 0, PALETTE_DIM, SLIDER_THICKNESS); + gradDraw.draw(mHorSlidersCv[2]); + } + + /** + * Report the correct tightly bounded dimensions of the view. + */ + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + setMeasuredDimension(VIEW_DIM_X, VIEW_DIM_Y); + } + + /** + * Convert a slider position in the range [0,PALETTE_DIM] to a byte value in the range [0,255]. + * @param sliderPos in the range [0,PALETTE_DIM]. + * @return + */ + public int sliderPosTo255(int sliderPos) { + int int255 = (int)(255.0f * ((float)sliderPos / (float)PALETTE_DIM)); + int255 = pinToByte(int255); + return int255; + } + + /** + * 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 a min and max range. + * @param n + * @return + */ + private int pinToByte(int n) { + if (n < 0) { + n = 0; + } else if (n > 255) { + n = 255; + } + return n; + } + + /** + * 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; + } + + /** + * Perform a matrix multiplication to convert between colorspaces, then scale the results by 255 on the assumption + * that the original output range was [0,1] and the required output range is [0,255]. + * @param a + * @param in + * @param out + */ + private void matrixProductToByte(float[] a, float[] in, int[] out) { + out[0] = pinToByte(round((a[0] * in[0] + a[1] * in[1] + a[2] * in[2]) * 255.0f)); + out[1] = pinToByte(round((a[5] * in[0] + a[6] * in[1] + a[7] * in[2]) * 255.0f)); + out[2] = pinToByte(round((a[10] * in[0] + a[11] * in[1] + a[12] * in[2]) * 255.0f)); + } + + /** + * 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); + } + + /** + * Unused, thought I might need this at one point. + * <P> + * If the integer quantization inherent in the android Color HSB/RGB conversions is problematic + * use this function instead. Problems can occur when converting back and forth, especially when done repeatedly. + * @param hsv in the range[0,360][0,1][0,1]. + * @param rgb in the range[0,1][0,1][0,1]. + */ + private void HSV2RGB(float[] hsv, float[] rgb) { + float f = hsv[0] / 60.0f - (int)hsv[0] / 60; + float p = hsv[2] * (1 - hsv[1]); + float q = hsv[2] * (1 - f * hsv[1]); + float t = hsv[2] * (1 - (1 - f) * hsv[1]); + switch (((int)hsv[0] / 60) % 6) { + case 0: + rgb[0] = hsv[2]; + rgb[1] = t; + rgb[2] = p; + break; + case 1: + rgb[0] = q; + rgb[1] = hsv[2]; + rgb[2] = p; + break; + case 2: + rgb[0] = p; + rgb[1] = hsv[2]; + rgb[2] = t; + break; + case 3: + rgb[0] = p; + rgb[1] = q; + rgb[2] = hsv[2]; + break; + case 4: + rgb[0] = t; + rgb[1] = p; + rgb[2] = hsv[2]; + break; + case 5: + rgb[0] = hsv[2]; + rgb[1] = p; + rgb[2] = q; + break; + } + } + + /** + * 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(); + + //Track the time so we don't switch between sliders too quickly + long currTime = Calendar.getInstance().getTimeInMillis(); + + //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; + case METHOD_HV_S_PALETTE: + if (mFocusedControl == 0) { + changeHVPalette(x, y, jump); + } + else if (mFocusedControl == 1) { + if (y < 0) + changeSlider(mFocusedControl, true, jump); + else if (y > 0) + changeSlider(mFocusedControl, false, jump); + } + break; + case METHOD_SV_H_PALETTE: + if (mFocusedControl == 0) { + changeSVPalette(x, y, jump); + } + else if (mFocusedControl == 1) { + if (y < 0) + changeSlider(mFocusedControl, true, jump); + else if (y > 0) + changeSlider(mFocusedControl, false, jump); + } + break; + case METHOD_UV_Y_PALETTE: + if (mFocusedControl == 0) { + changeUVPalette(x, y, jump); + } + else if (mFocusedControl == 1) { + if (y < 0) + changeSlider(mFocusedControl, true, jump); + else if (y > 0) + changeSlider(mFocusedControl, false, jump); + } + break; + case METHOD_RGB_SLIDERS: + case METHOD_HSV_SLIDERS: + case METHOD_YUV_SLIDERS: + if (y < 0) { + if (mFocusedControl == -1) { + mFocusedControl = 2; + invalidate(); + } + else if (mFocusedControl > 0 && currTime - mTimeOfLastSliderSwitch > 200) { + mTimeOfLastSliderSwitch = currTime; + mFocusedControl--; + invalidate(); + } + } + else if (y > 0) { + if (mFocusedControl == -1) { + mFocusedControl = 0; + invalidate(); + } + else if (mFocusedControl < 2 && currTime - mTimeOfLastSliderSwitch > 200) { + mTimeOfLastSliderSwitch = currTime; + mFocusedControl++; + invalidate(); + } + } + else if (x < 0 && mFocusedControl != -1) { + changeSlider(mFocusedControl, false, jump); + } + else if (x > 0 && mFocusedControl != -1) { + changeSlider(mFocusedControl, true, 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 = (float)java.lang.Math.sqrt(mCoord[0] * mCoord[0] + mCoord[1] * mCoord[1]); + if (radius > PALETTE_RADIUS) + radius = PALETTE_RADIUS; + + float angle = (float)java.lang.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(Math.cos(angle) * radius); + mCoord[1] = round(Math.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 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 changeHVPalette(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[2] += y2; + + if (mCoord[0] < -PALETTE_RADIUS) + mCoord[0] = -PALETTE_RADIUS; + else if (mCoord[0] > PALETTE_RADIUS) + mCoord[0] = PALETTE_RADIUS; + if (mCoord[2] < -PALETTE_RADIUS) + mCoord[2] = -PALETTE_RADIUS; + else if (mCoord[2] > PALETTE_RADIUS) + mCoord[2] = PALETTE_RADIUS; + + float radius = (float)java.lang.Math.sqrt(mCoord[0] * mCoord[0] + mCoord[2] * mCoord[2]); + if (radius > PALETTE_RADIUS) + radius = PALETTE_RADIUS; + + float angle = (float)java.lang.Math.atan2(mCoord[2], 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(Math.cos(angle) * radius); + mCoord[2] = round(Math.sin(angle) * radius); + + int c = interpColor(mSpectrumColorsRev, unit); + float[] hsv = new float[3]; + Color.colorToHSV(c, hsv); + mHSV[0] = hsv[0]; + mHSV[2] = radius / PALETTE_RADIUS; + updateAllFromHSV(); + mSwatchNew.setColor(Color.HSVToColor(mHSV)); + + setOvalSatFader(); + setVerSatSlider(); + + invalidate(); + } + + /** + * 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 changeSVPalette(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[1] += y2; + mCoord[2] += x2; + + mCoord[1] = (int)pin(mCoord[1], PALETTE_DIM); + mCoord[2] = (int)pin(mCoord[2], PALETTE_DIM); + + mHSV[1] = (float)(PALETTE_DIM - mCoord[1]) / (float)PALETTE_DIM; + mHSV[2] = (float)mCoord[2] / (float)PALETTE_DIM; + updateAllFromHSV(); + mSwatchNew.setColor(Color.HSVToColor(mHSV)); + + setHorSatFader(); + setHorValDimmer(); + + invalidate(); + } + + /** + * 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 changeUVPalette(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[1] += x2; + mCoord[2] += y2; + + mCoord[1] = (int)pin(mCoord[1], PALETTE_DIM); + mCoord[2] = (int)pin(mCoord[2], PALETTE_DIM); + + mYUV[1] = ((float)mCoord[1] / (float)PALETTE_DIM) - .5f; + mYUV[2] = ((float)(PALETTE_DIM - mCoord[2]) / (float)PALETTE_DIM) - .5f; + updateAllFromYUV(); + + ColorMatrix cm = new ColorMatrix(); + cm.setYUV2RGB(); + final float[] a = cm.getArray(); + int[] rgb = new int[3]; + matrixProductToByte(a, mYUV, rgb); + mSwatchNew.setColor(Color.rgb(rgb[0], rgb[1], rgb[2])); + + setVerYSlider(); + + 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(); + } + else if (mMethod == METHOD_HV_S_PALETTE) { + //slider *must* equal 1 + + mHSV[1] += (increase ? jump : -jump) / 256.0f; + mHSV[1] = pinToUnit(mHSV[1]); + updateAllFromHSV(); + mCoord[1] = PALETTE_DIM - (int)(mHSV[1] * PALETTE_DIM); + + mSwatchNew.setColor(Color.HSVToColor(mHSV)); + + setOvalSatFader(); + + invalidate(); + } + else if (mMethod == METHOD_SV_H_PALETTE) { + //slider *must* equal 1 + + mHSV[0] += ((increase ? jump : -jump) / 256.0f) * 360.0f; + mHSV[0] = pin(mHSV[0], 360); + updateAllFromHSV(); + mCoord[0] = PALETTE_DIM - (int)((mHSV[0] / 360.0f) * PALETTE_DIM); + + mSwatchNew.setColor(Color.HSVToColor(mHSV)); + + setSatValPalette(); + + invalidate(); + } + else if (mMethod == METHOD_UV_Y_PALETTE) { + //slider *must* equal 1 + + mYUV[0] += (increase ? jump : -jump) / 256.0f; + mYUV[0] = pinToUnit(mYUV[0]); + updateAllFromYUV(); + mCoord[0] = PALETTE_DIM - (int)(mYUV[0] * PALETTE_DIM); + + mSwatchNew.setColor(Color.HSVToColor(mHSV)); + + setUVPalette(); + + invalidate(); + } + else if (mMethod == METHOD_RGB_SLIDERS) { + int color = Color.HSVToColor(mHSV); + if (slider == 0) + mRGB[slider] = Color.red(color) + (increase ? jump : -jump); + else if (slider == 1) + mRGB[slider] = Color.green(color) + (increase ? jump : -jump); + else mRGB[slider] = Color.blue(color) + (increase ? jump : -jump); + + mRGB[slider] = pinToByte(mRGB[slider]); + updateAllFromRGB(); + mCoord[slider] = (int)(PALETTE_DIM * (mRGB[slider] / 255.0f)); + + mSwatchNew.setColor(Color.HSVToColor(mHSV)); + + if (slider != 0) + setHorRSlider(); + if (slider != 1) + setHorGSlider(); + if (slider != 2) + setHorBSlider(); + + invalidate(); + } + else if (mMethod == METHOD_HSV_SLIDERS) { + if (slider == 0) { + mHSV[slider] += ((increase ? jump : -jump) / 256.0f) * 360.0f; + mHSV[slider] = pin(mHSV[slider], 360); + mCoord[slider] = (int)((mHSV[slider] / 360.0f) * PALETTE_DIM); + } + else { + mHSV[slider] += (increase ? jump : -jump) / 256.0f; + mHSV[slider] = pinToUnit(mHSV[slider]); + mCoord[slider] = (int)(mHSV[slider] * PALETTE_DIM); + } + + updateAllFromHSV(); + mSwatchNew.setColor(Color.HSVToColor(mHSV)); + + if (slider == 0) { + setHorSatSlider(); + setHorValSlider(); + } + else if (slider == 1) { + setHorSatFader(); + setHorValSlider(); + } + else if (slider == 2) { + setHorValDimmer(); + setHorSatSlider(); + } + + invalidate(); + } + else if (mMethod == METHOD_YUV_SLIDERS) { + mYUV[slider] += (increase ? jump : -jump) / 256.0f; + if (slider == 0) + mYUV[slider] = pinToUnit(mYUV[slider]); + else mYUV[slider] = pin(mYUV[slider], -.5f, .5f); + + updateAllFromYUV(); + + if (slider == 0) + mCoord[slider] = (int)(mYUV[slider] * PALETTE_DIM); + else mCoord[slider] = (int)((mYUV[slider] + .5f) * PALETTE_DIM); + + mSwatchNew.setColor(Color.HSVToColor(mHSV)); + + if (slider != 0) + setHorYSlider(); + if (slider != 1) + setHorUSlider(); + if (slider != 2) + setHorVSlider(); + + invalidate(); + } + } + + //NEW_METHOD_WORK_NEEDED_HERE + //If the new method doesn't operate on HSV (specifically, on the variable mHSV), and also doesn't operate on + //mRGB or mYUV, which are already implemented here, then the pattern below needs to be replicated for the additional colorspace. + //Namely, it is critical that all representations (HSV, RGB, YUV, Hex, etc.) be maintained at all times. + /** + * Keep all colorspace representations in sync. + */ + private void updateHSVfromRGB() { + Color.RGBToHSV(mRGB[0], mRGB[1], mRGB[2], mHSV); + if (isGray(mRGB)) + mHSV[1] = 0; + } + + /** + * Keep all colorspace representations in sync. + */ + private void updateHSVfromYUV() { + ColorMatrix cm = new ColorMatrix(); + cm.setYUV2RGB(); + final float[] a = cm.getArray(); + matrixProductToByte(a, mYUV, mRGB); + updateHSVfromRGB(); + } + + /** + * 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 updateRGBfromYUV() { + ColorMatrix cm = new ColorMatrix(); + cm.setYUV2RGB(); + final float[] a = cm.getArray(); + matrixProductToByte(a, mYUV, mRGB); + } + + /** + * 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(); + } + + /** + * Keep all colorspace representations in sync. + */ + private void updateAllFromRGB() { + //Update mHSV + if (mHSVenabled || mHexenabled) + updateHSVfromRGB(); + + //Update mYUV + if (mYUVenabled) + updateYUVfromRGB(); + + //Update mHexStr + if (mHexenabled) + updateHexFromHSV(); + } + + /** + * Keep all colorspace representations in sync. + */ + private void updateAllFromYUV() { + //Update mRGB + if (mRGBenabled || mHSVenabled || mHexenabled) + updateRGBfromYUV(); + + //Update mYUV + if (mHSVenabled) + updateHSVfromRGB(); + + //Update mHexStr + if (mHexenabled) + 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 x2 = (int)(pin(round(x - PALETTE_POS_X), PALETTE_DIM)); + 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); + + //Is the event in a method selector icon? + boolean inMethodSelector[] = new boolean[NUM_ENABLED_METHODS]; + for (int i = 0; i < NUM_ENABLED_METHODS; i++) + inMethodSelector[i] = false; + for (int i = 0; i < NUM_ENABLED_METHODS; i++) { + if (ptInRect(round(x), round(y), mMethodSelectorRects[i])) { + inMethodSelector[i] = true; + break; + } + } + + //Get the event's distance from the center of the main 2D palette + float radius = (float)java.lang.Math.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 square palette + boolean inSquarePalette = ptInRect(round(x), round(y), mPaletteRect); + + //Is the event in a vertical slider to the right of the main 2D palette + boolean inVerSlider = ptInRect(round(x), round(y), mVerSliderRect); + + //Is the event in a horizontal slider within the main "palette's" region + boolean inFirstHorSlider = ptInRect(round(x), round(y), mHorSliderRects[0]); + boolean inSecondHorSlider = ptInRect(round(x), round(y), mHorSliderRects[1]); + boolean inThirdHorSlider = ptInRect(round(x), round(y), mHorSliderRects[2]); + + 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 (NUM_ENABLED_METHODS > 0 && inMethodSelector[0]) + mTracking = mMethodSelectRectMap[0]; + else if (NUM_ENABLED_METHODS > 1 && inMethodSelector[1]) + mTracking = mMethodSelectRectMap[1]; + else if (NUM_ENABLED_METHODS > 2 && inMethodSelector[2]) + mTracking = mMethodSelectRectMap[2]; + else if (NUM_ENABLED_METHODS > 3 && inMethodSelector[3]) + mTracking = mMethodSelectRectMap[3]; + else if (NUM_ENABLED_METHODS > 4 && inMethodSelector[4]) + mTracking = mMethodSelectRectMap[4]; + else if (NUM_ENABLED_METHODS > 5 && inMethodSelector[5]) + mTracking = mMethodSelectRectMap[5]; + else if (NUM_ENABLED_METHODS > 6 && inMethodSelector[6]) + mTracking = mMethodSelectRectMap[6]; + + //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; + } + } + else if (mMethod == METHOD_HV_S_PALETTE) { + if (inOvalPalette) { + mTracking = TRACK_HV_PALETTE; + mFocusedControl = 0; + } + else if (inVerSlider) { + mTracking = TRACK_VER_S_SLIDER; + mFocusedControl = 1; + } + } + else if (mMethod == METHOD_SV_H_PALETTE) { + if (inSquarePalette) { + mTracking = TRACK_SV_PALETTE; + mFocusedControl = 0; + } + else if (inVerSlider) { + mTracking = TRACK_VER_H_SLIDER; + mFocusedControl = 1; + } + } + else if (mMethod == METHOD_UV_Y_PALETTE) { + if (inSquarePalette) { + mTracking = TRACK_UV_PALETTE; + mFocusedControl = 0; + } + else if (inVerSlider) { + mTracking = TRACK_VER_Y_SLIDER; + mFocusedControl = 1; + } + } + else if (mMethod == METHOD_RGB_SLIDERS) { + if (inFirstHorSlider) { + mTracking = TRACK_R_SLIDER; + mFocusedControl = 0; + } + else if (inSecondHorSlider) { + mTracking = TRACK_G_SLIDER; + mFocusedControl = 1; + } + else if (inThirdHorSlider) { + mTracking = TRACK_B_SLIDER; + mFocusedControl = 2; + } + } + else if (mMethod == METHOD_HSV_SLIDERS) { + if (inFirstHorSlider) { + mTracking = TRACK_H_SLIDER; + mFocusedControl = 0; + } + else if (inSecondHorSlider) { + mTracking = TRACK_S_SLIDER; + mFocusedControl = 1; + } + else if (inThirdHorSlider) { + mTracking = TRACK_HOR_VALUE_SLIDER; + mFocusedControl = 2; + } + } + else if (mMethod == METHOD_YUV_SLIDERS) { + if (inFirstHorSlider) { + mTracking = TRACK_HOR_Y_SLIDER; + mFocusedControl = 0; + } + else if (inSecondHorSlider) { + mTracking = TRACK_U_SLIDER; + mFocusedControl = 1; + } + else if (inThirdHorSlider) { + mTracking = TRACK_V_SLIDER; + mFocusedControl = 2; + } + } + 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(Math.cos(angle) * radius); + mCoord[1] = round(Math.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(); + } + } + else if (mTracking == TRACK_HV_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(Math.cos(angle) * radius); + mCoord[2] = round(Math.sin(angle) * radius); + + int c = interpColor(mSpectrumColorsRev, unit); + float[] hsv = new float[3]; + Color.colorToHSV(c, hsv); + mHSV[0] = hsv[0]; + mHSV[2] = radius / PALETTE_RADIUS; + updateAllFromHSV(); + mSwatchNew.setColor(Color.HSVToColor(mHSV)); + + setOvalSatFader(); + setVerSatSlider(); + + invalidate(); + } + else if (mTracking == TRACK_VER_S_SLIDER) { + if (mCoord[1] != y2) { + mCoord[1] = y2; + float value = 1.0f - (float)y2 / (float)PALETTE_DIM; + + mHSV[1] = value; + updateAllFromHSV(); + mSwatchNew.setColor(Color.HSVToColor(mHSV)); + + setOvalSatFader(); + + invalidate(); + } + } + else if (mTracking == TRACK_SV_PALETTE) { + if (mCoord[1] != y2 || mCoord[2] != x2) { + mCoord[1] = y2; + mCoord[2] = x2; + + mHSV[1] = (float)(PALETTE_DIM - mCoord[1]) / (float)PALETTE_DIM; + mHSV[2] = (float)mCoord[2] / (float)PALETTE_DIM; + updateAllFromHSV(); + mSwatchNew.setColor(Color.HSVToColor(mHSV)); + + setHorSatFader(); + setHorValDimmer(); + + invalidate(); + } + } + else if (mTracking == TRACK_VER_H_SLIDER) { + if (mCoord[0] != y2) { + mCoord[0] = y2; + float hue = 360.0f - 360.0f * ((float)y2 / (float)PALETTE_DIM); + + mHSV[0] = hue; + updateAllFromHSV(); + mSwatchNew.setColor(Color.HSVToColor(mHSV)); + + setSatValPalette(); + + invalidate(); + } + } + else if (mTracking == TRACK_UV_PALETTE) { + if (mCoord[1] != y2 || mCoord[2] != x2) { + mCoord[1] = x2; + mCoord[2] = y2; + + mYUV[1] = ((float)mCoord[1] / (float)PALETTE_DIM) - .5f; + mYUV[2] = ((float)(PALETTE_DIM - mCoord[2]) / (float)PALETTE_DIM) - .5f; + updateAllFromYUV(); + mSwatchNew.setColor(Color.HSVToColor(mHSV)); + + setVerYSlider(); + + invalidate(); + } + } + else if (mTracking == TRACK_VER_Y_SLIDER) { + if (mCoord[0] != y2) { + mCoord[0] = y2; + + mYUV[0] = 1.0f - (float)mCoord[0] / (float)PALETTE_DIM; + updateAllFromYUV(); + mSwatchNew.setColor(Color.HSVToColor(mHSV)); + + setUVPalette(); + + invalidate(); + } + } + else if (mTracking == TRACK_R_SLIDER) { + if (mCoord[0] != x2) { + mCoord[0] = x2; + int int255 = sliderPosTo255(mCoord[0]); + + mRGB[0] = int255; + updateAllFromRGB(); + mSwatchNew.setColor(Color.HSVToColor(mHSV)); + + setHorGSlider(); + setHorBSlider(); + + invalidate(); + } + } + else if (mTracking == TRACK_G_SLIDER) { + if (mCoord[1] != x2) { + mCoord[1] = x2; + int int255 = sliderPosTo255(mCoord[1]); + + mRGB[1] = int255; + updateAllFromRGB(); + mSwatchNew.setColor(Color.HSVToColor(mHSV)); + + setHorRSlider(); + setHorBSlider(); + + invalidate(); + } + } + else if (mTracking == TRACK_B_SLIDER) { + if (mCoord[2] != x2) { + mCoord[2] = x2; + int int255 = sliderPosTo255(mCoord[2]); + + mRGB[2] = int255; + updateAllFromRGB(); + mSwatchNew.setColor(Color.HSVToColor(mHSV)); + + setHorRSlider(); + setHorGSlider(); + + invalidate(); + } + } + else if (mTracking == TRACK_H_SLIDER) { + if (mCoord[0] != x2) { + mCoord[0] = x2; + float hue = 360.0f * ((float)mCoord[0] / (float)PALETTE_DIM); + + mHSV[0] = hue; + updateAllFromHSV(); + mSwatchNew.setColor(Color.HSVToColor(mHSV)); + + setHorSatSlider(); + setHorValSlider(); + + invalidate(); + } + } + else if (mTracking == TRACK_S_SLIDER) { + if (mCoord[1] != x2) { + mCoord[1] = x2; + float sat = (float)mCoord[1] / (float)PALETTE_DIM; + + mHSV[1] = sat; + updateAllFromHSV(); + mSwatchNew.setColor(Color.HSVToColor(mHSV)); + + setHorSatFader(); + setHorValSlider(); + + invalidate(); + } + } + else if (mTracking == TRACK_HOR_VALUE_SLIDER) { + if (mCoord[2] != x2) { + mCoord[2] = x2; + float val = (float)mCoord[2] / (float)PALETTE_DIM; + + mHSV[2] = val; + updateAllFromHSV(); + mSwatchNew.setColor(Color.HSVToColor(mHSV)); + + setHorValDimmer(); + setHorSatSlider(); + + invalidate(); + } + } + else if (mTracking == TRACK_HOR_Y_SLIDER) { + if (mCoord[0] != x2) { + mCoord[0] = x2; + + mYUV[0] = (float)mCoord[0] / (float)PALETTE_DIM; + updateAllFromYUV(); + mSwatchNew.setColor(Color.HSVToColor(mHSV)); + + setHorUSlider(); + setHorVSlider(); + + invalidate(); + } + } + else if (mTracking == TRACK_U_SLIDER) { + if (mCoord[1] != x2) { + mCoord[1] = x2; + + mYUV[1] = ((float)mCoord[1] / (float)PALETTE_DIM) - .5f; + updateAllFromYUV(); + mSwatchNew.setColor(Color.HSVToColor(mHSV)); + + setHorYSlider(); + setHorVSlider(); + + invalidate(); + } + } + else if (mTracking == TRACK_V_SLIDER) { + if (mCoord[2] != x2) { + mCoord[2] = x2; + + mYUV[2] = ((float)mCoord[2] / (float)PALETTE_DIM) - .5f; + updateAllFromYUV(); + mSwatchNew.setColor(Color.HSVToColor(mHSV)); + + setHorYSlider(); + setHorUSlider(); + + 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); + if (isGray(mOriginalColor)) + mHSV[1] = 0; + mSwatchNew.setColor(mOriginalColor); + initUI(); + invalidate(); + } + else if (mTracking == TRACK_SWATCH_NEW && inSwatchNew) { + mListener.colorChanged(mSwatchNew.getColor()); + invalidate(); + } + else if (NUM_ENABLED_METHODS > 0 && mTracking == mMethodSelectRectMap[0] && inMethodSelector[0]) { + mMethod = mMethodSelectRectMap[0]; + initUI(); + invalidate(); + } + else if (NUM_ENABLED_METHODS > 1 && mTracking == mMethodSelectRectMap[1] && inMethodSelector[1]) { + mMethod = mMethodSelectRectMap[1]; + initUI(); + invalidate(); + } + else if (NUM_ENABLED_METHODS > 2 && mTracking == mMethodSelectRectMap[2] && inMethodSelector[2]) { + mMethod = mMethodSelectRectMap[2]; + initUI(); + invalidate(); + } + else if (NUM_ENABLED_METHODS > 3 && mTracking == mMethodSelectRectMap[3] && inMethodSelector[3]) { + mMethod = mMethodSelectRectMap[3]; + initUI(); + invalidate(); + } + else if (NUM_ENABLED_METHODS > 4 && mTracking == mMethodSelectRectMap[4] && inMethodSelector[4]) { + mMethod = mMethodSelectRectMap[4]; + initUI(); + invalidate(); + } + else if (NUM_ENABLED_METHODS > 5 && mTracking == mMethodSelectRectMap[5] && inMethodSelector[5]) { + mMethod = mMethodSelectRectMap[5]; + initUI(); + invalidate(); + } + else if (NUM_ENABLED_METHODS > 6 && mTracking == mMethodSelectRectMap[6] && inMethodSelector[6]) { + mMethod = mMethodSelectRectMap[6]; + initUI(); + invalidate(); + } + + mTracking= TRACKED_NONE; + break; + } + + return true; + } + } +} |