diff options
author | paulnavin@google.com <paulnavin@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-05-30 19:18:42 +0000 |
---|---|---|
committer | paulnavin@google.com <paulnavin@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-05-30 19:18:42 +0000 |
commit | d9ef4868006db4b38143a45f4c2a1d8cf8b55a26 (patch) | |
tree | 0040d325360d6cc80e53cbd5b91b4e43c6e2f685 | |
parent | 34697d6bc62ac214409d9efe855b1af5c6131ef1 (diff) | |
download | chromium_src-d9ef4868006db4b38143a45f4c2a1d8cf8b55a26.zip chromium_src-d9ef4868006db4b38143a45f4c2a1d8cf8b55a26.tar.gz chromium_src-d9ef4868006db4b38143a45f4c2a1d8cf8b55a26.tar.bz2 |
Color Picker for <input type=color>
Thanks for all the review comments! I think this is now ready. :-)
I've replied to as many comments as possible, I hope my replies make
sense. Thanks again, I'm learning a lot! :-)
References:
- The mockup I'm following is here:
https://docs.google.com/a/google.com/file/d/0B2S_KwX0QwGFb2JVTlhPYlRqaEE/edit
- There's an APK here:
https://drive.google.com/a/google.com/folderview?id=0B8nL-JlqpL9nLURUVnJsdTFDYjQ
BUG=135771
Review URL: https://chromiumcodereview.appspot.com/14170009
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@203195 0039d316-1c4b-4281-b951-d872f2087c98
12 files changed, 808 insertions, 179 deletions
diff --git a/components/web_contents_delegate_android/android/java/src/org/chromium/components/web_contents_delegate_android/ColorChooserAndroid.java b/components/web_contents_delegate_android/android/java/src/org/chromium/components/web_contents_delegate_android/ColorChooserAndroid.java index a47e0de..babf91e 100644 --- a/components/web_contents_delegate_android/android/java/src/org/chromium/components/web_contents_delegate_android/ColorChooserAndroid.java +++ b/components/web_contents_delegate_android/android/java/src/org/chromium/components/web_contents_delegate_android/ColorChooserAndroid.java @@ -10,6 +10,7 @@ import org.chromium.base.CalledByNative; import org.chromium.base.JNINamespace; import org.chromium.content.browser.ContentViewCore; import org.chromium.ui.ColorPickerDialog; +import org.chromium.ui.OnColorChangedListener; /** * ColorChooserAndroid communicates with the java ColorPickerDialog and the @@ -22,11 +23,9 @@ public class ColorChooserAndroid { private ColorChooserAndroid(int nativeColorChooserAndroid, Context context, int initialColor) { - ColorPickerDialog.OnColorChangedListener listener = - new ColorPickerDialog.OnColorChangedListener() { - + OnColorChangedListener listener = new OnColorChangedListener() { @Override - public void colorChanged(int color) { + public void onColorChanged(int color) { mDialog.dismiss(); nativeOnColorChosen(mNativeColorChooserAndroid, color); } diff --git a/ui/android/java/res/drawable/color_picker_border.xml b/ui/android/java/res/drawable/color_picker_border.xml new file mode 100644 index 0000000..87c0feb --- /dev/null +++ b/ui/android/java/res/drawable/color_picker_border.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2013 The Chromium Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. +--> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <solid android:color="#00000000"/> + <stroke android:width="1px" android:color="#D0D0D0" /> +</shape>
\ No newline at end of file diff --git a/ui/android/java/res/layout/color_picker_advanced_component.xml b/ui/android/java/res/layout/color_picker_advanced_component.xml new file mode 100644 index 0000000..a51c055 --- /dev/null +++ b/ui/android/java/res/layout/color_picker_advanced_component.xml @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2013 The Chromium Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. +--> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + + <TextView + android:id="@+id/text" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/color_picker_gradient_margin" + android:layout_marginEnd="@dimen/color_picker_gradient_margin" + android:textAppearance="@android:style/TextAppearance.Medium" + android:textIsSelectable="false" /> + + <FrameLayout + android:id="@+id/gradient_border" + android:layout_width="match_parent" + android:layout_height="50dp" + android:layout_below="@id/text" + android:layout_marginStart="@dimen/color_picker_gradient_margin" + android:layout_marginEnd="@dimen/color_picker_gradient_margin" + android:layout_marginTop="3dp" + android:background="@drawable/color_picker_border" + android:padding="1dp" > + + <View + android:id="@+id/gradient" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + </FrameLayout> + + <SeekBar + android:id="@+id/seek_bar" + android:layout_width="match_parent" + android:layout_height="75dp" + android:layout_below="@id/text" + android:progressDrawable="@android:color/transparent" + android:thumb="@drawable/color_picker_advanced_select_handle" + android:translationY="25dp" /> + +</RelativeLayout>
\ No newline at end of file diff --git a/ui/android/java/res/layout/color_picker_dialog_content.xml b/ui/android/java/res/layout/color_picker_dialog_content.xml new file mode 100644 index 0000000..d07ce78 --- /dev/null +++ b/ui/android/java/res/layout/color_picker_dialog_content.xml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2013 The Chromium Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. +--> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:padding="15dp"> + + <org.chromium.ui.ColorPickerAdvanced + android:id="@+id/color_picker_advanced" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + <FrameLayout + android:id="@+id/color_picker_simple_border" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@drawable/color_picker_border" + android:padding="1dp"> + + <org.chromium.ui.ColorPickerSimple + android:id="@+id/color_picker_simple" + android:layout_width="match_parent" + android:layout_height="100dp"/> + </FrameLayout> + + <FrameLayout + android:id="@+id/more_colors_button_border" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@+id/color_picker_simple_border" + android:background="@drawable/color_picker_border" + android:padding="1dp"> + + <Button + android:id="@+id/more_colors_button" + style="?android:attr/buttonBarButtonStyle" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="60dip" + android:text="@string/color_picker_button_more" /> + </FrameLayout> + +</RelativeLayout>
\ No newline at end of file diff --git a/ui/android/java/res/layout/color_picker_dialog_title.xml b/ui/android/java/res/layout/color_picker_dialog_title.xml new file mode 100644 index 0000000..cb5de4e --- /dev/null +++ b/ui/android/java/res/layout/color_picker_dialog_title.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2013 The Chromium Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. +--> + +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingBottom="15dp" + android:paddingStart="20dp" + android:paddingEnd="20dp" + android:paddingTop="15dp" > + + <TextView + android:id="@+id/title" + style="?android:attr/textAppearanceLarge" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_toStartOf="@+id/selected_color_view_border" + android:layout_centerVertical="true" + android:ellipsize="end" + android:singleLine="true" /> + + <FrameLayout + android:layout_width="40dp" + android:layout_height="40dp" + android:layout_alignParentEnd="true" + android:id="@+id/selected_color_view_border" + android:background="@drawable/color_picker_border" + android:padding="1dp"> + + <View + android:id="@+id/selected_color_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@android:color/black" /> + </FrameLayout> + +</RelativeLayout>
\ No newline at end of file diff --git a/ui/android/java/res/values/dimens.xml b/ui/android/java/res/values/dimens.xml new file mode 100644 index 0000000..ed86372 --- /dev/null +++ b/ui/android/java/res/values/dimens.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2013 The Chromium Authors. All rights reserved. + + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. +--> +<resources> + + <!-- + 14.5 = Seekbar thumb width - border width, but it depends on the width + of the seek bar icon. + --> + <dimen name="color_picker_gradient_margin">14.5dp</dimen> + +</resources>
\ No newline at end of file diff --git a/ui/android/java/src/org/chromium/ui/ColorPickerAdvanced.java b/ui/android/java/src/org/chromium/ui/ColorPickerAdvanced.java new file mode 100644 index 0000000..7a22f61 --- /dev/null +++ b/ui/android/java/src/org/chromium/ui/ColorPickerAdvanced.java @@ -0,0 +1,252 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.ui; + +import android.content.Context; +import android.graphics.Color; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.accessibility.AccessibilityEvent; +import android.widget.LinearLayout; +import android.widget.SeekBar; +import android.widget.SeekBar.OnSeekBarChangeListener; + +/** + * Represents a more advanced way for the user to choose a color, based on selecting each of + * the Hue, Saturation and Value attributes. + */ +public class ColorPickerAdvanced extends LinearLayout implements OnSeekBarChangeListener { + private static final int HUE_SEEK_BAR_MAX = 360; + + private static final int HUE_COLOR_COUNT = 7; + + private static final int SATURATION_SEEK_BAR_MAX = 100; + + private static final int SATURATION_COLOR_COUNT = 2; + + private static final int LIGHTNESS_SEEK_BAR_MAX = 100; + + private static final int LIGHTNESS_COLOR_COUNT = 2; + + ColorPickerAdvancedComponent mHueDetails; + + ColorPickerAdvancedComponent mSaturationDetails; + + ColorPickerAdvancedComponent mLightnessDetails; + + private OnColorChangedListener mOnColorChangedListener; + + private int mCurrentColor; + + private final float[] mCurrentHsvValues = new float[3]; + + public ColorPickerAdvanced(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public ColorPickerAdvanced(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + public ColorPickerAdvanced(Context context) { + super(context); + init(); + } + + /** + * Initializes all the views and variables in the advanced view. + */ + private void init() { + setOrientation(LinearLayout.VERTICAL); + + mHueDetails = createAndAddNewGradient(R.string.color_picker_hue, + HUE_SEEK_BAR_MAX, this); + mSaturationDetails = createAndAddNewGradient(R.string.color_picker_saturation, + SATURATION_SEEK_BAR_MAX, this); + mLightnessDetails = createAndAddNewGradient(R.string.color_picker_lightness, + LIGHTNESS_SEEK_BAR_MAX, this); + + refreshGradientComponents(); + } + + /** + * Creates a new GradientDetails object from the parameters provided, initializes it, + * and adds it to this advanced view. + * + * @param textResourceId The text to display for the label. + * @param seekBarMax The maximum value of the seek bar for the gradient. + * @param seekBarListener Object listening to when the user changes the seek bar. + * + * @return A new GradientDetails object initialized with the given parameters. + */ + public ColorPickerAdvancedComponent createAndAddNewGradient(int textResourceId, + int seekBarMax, + OnSeekBarChangeListener seekBarListener) { + LayoutInflater inflater = (LayoutInflater) getContext() + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + View newComponent = inflater.inflate(R.layout.color_picker_advanced_component, null); + addView(newComponent); + + return new ColorPickerAdvancedComponent(newComponent, + textResourceId, + seekBarMax, + seekBarListener); + } + + /** + * Sets the listener for when the user changes the color. + * + * @param onColorChangedListener The object listening for the change in color. + */ + public void setListener(OnColorChangedListener onColorChangedListener) { + mOnColorChangedListener = onColorChangedListener; + } + + /** + * @return The color the user has currently chosen. + */ + public int getColor() { + return mCurrentColor; + } + + /** + * Sets the color that the user has currently chosen. + * + * @param color The currently chosen color. + */ + public void setColor(int color) { + mCurrentColor = color; + Color.colorToHSV(mCurrentColor, mCurrentHsvValues); + refreshGradientComponents(); + } + + /** + * Notifies the listener, if there is one, of a change in the selected color. + */ + private void notifyColorChanged() { + if (mOnColorChangedListener != null) { + mOnColorChangedListener.onColorChanged(getColor()); + } + } + + /** + * Callback for when a slider is updated on the advanced view. + * + * @param seekBar The color slider that was updated. + * @param progress The new value of the color slider. + * @param fromUser Whether it was the user the changed the value, or whether + * we were setting it up. + */ + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (fromUser) { + mCurrentHsvValues[0] = mHueDetails.getValue(); + mCurrentHsvValues[1] = mSaturationDetails.getValue() / 100.0f; + mCurrentHsvValues[2] = mLightnessDetails.getValue() / 100.0f; + + mCurrentColor = Color.HSVToColor(mCurrentHsvValues); + + updateHueGradient(); + updateSaturationGradient(); + updateLightnessGradient(); + + notifyColorChanged(); + } + } + + /** + * Updates only the hue gradient display with the hue value for the + * currently selected color. + */ + private void updateHueGradient() { + float[] tempHsvValues = new float[3]; + tempHsvValues[1] = mCurrentHsvValues[1]; + tempHsvValues[2] = mCurrentHsvValues[2]; + + int[] newColors = new int[HUE_COLOR_COUNT]; + + for (int i = 0; i < HUE_COLOR_COUNT; ++i) { + tempHsvValues[0] = i * 60.0f; + newColors[i] = Color.HSVToColor(tempHsvValues); + } + mHueDetails.setGradientColors(newColors); + } + + /** + * Updates only the saturation gradient display with the saturation value + * for the currently selected color. + */ + private void updateSaturationGradient() { + float[] tempHsvValues = new float[3]; + tempHsvValues[0] = mCurrentHsvValues[0]; + tempHsvValues[1] = 0.0f; + tempHsvValues[2] = mCurrentHsvValues[2]; + + int[] newColors = new int[SATURATION_COLOR_COUNT]; + + newColors[0] = Color.HSVToColor(tempHsvValues); + + tempHsvValues[1] = 1.0f; + newColors[1] = Color.HSVToColor(tempHsvValues); + mSaturationDetails.setGradientColors(newColors); + } + + /** + * Updates only the lightness gradient display with the lightness value for + * the currently selected color. + */ + private void updateLightnessGradient() { + float[] tempHsvValues = new float[3]; + tempHsvValues[0] = mCurrentHsvValues[0]; + tempHsvValues[1] = mCurrentHsvValues[1]; + tempHsvValues[2] = 0.0f; + + int[] newColors = new int[LIGHTNESS_COLOR_COUNT]; + + newColors[0] = Color.HSVToColor(tempHsvValues); + + tempHsvValues[2] = 1.0f; + newColors[1] = Color.HSVToColor(tempHsvValues); + mLightnessDetails.setGradientColors(newColors); + } + + /** + * Updates all the gradient displays to show the currently selected color. + */ + private void refreshGradientComponents() { + // Round and bound the saturation value. + int saturationValue = Math.round(mCurrentHsvValues[1] * 100.0f); + saturationValue = Math.min(saturationValue, SATURATION_SEEK_BAR_MAX); + saturationValue = Math.max(saturationValue, 0); + + // Round and bound the lightness value. + int lightnessValue = Math.round(mCurrentHsvValues[2] * 100.0f); + lightnessValue = Math.min(lightnessValue, LIGHTNESS_SEEK_BAR_MAX); + lightnessValue = Math.max(lightnessValue, 0); + + // Don't need to round the hue value since its possible values match the seek bar + // range directly. + mHueDetails.setValue(mCurrentHsvValues[0]); + mSaturationDetails.setValue(saturationValue); + mLightnessDetails.setValue(lightnessValue); + + updateHueGradient(); + updateSaturationGradient(); + updateLightnessGradient(); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + // Do nothing. + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + // Do nothing. + } +} diff --git a/ui/android/java/src/org/chromium/ui/ColorPickerAdvancedComponent.java b/ui/android/java/src/org/chromium/ui/ColorPickerAdvancedComponent.java new file mode 100644 index 0000000..259b88c --- /dev/null +++ b/ui/android/java/src/org/chromium/ui/ColorPickerAdvancedComponent.java @@ -0,0 +1,81 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.ui; + +import android.graphics.drawable.GradientDrawable; +import android.view.View; +import android.widget.SeekBar; +import android.widget.SeekBar.OnSeekBarChangeListener; +import android.widget.TextView; + +/** + * Encapsulates a single gradient view of the HSV color display, including its label, gradient + * view and seek bar. + * + * Mirrors a "color_picker_advanced_component" layout. + */ +public class ColorPickerAdvancedComponent { + // The view that displays the gradient. + private final View mGradientView; + // The seek bar that allows the user to change the value of this component. + private final SeekBar mSeekBar; + // The set of colors to interpolate the gradient through. + private int[] mGradientColors; + // The Drawable that represents the gradient. + private final GradientDrawable mGradientDrawable; + // The text label for the component. + private final TextView mText; + + /** + * Initializes the views. + * + * @param rootView View that contains all the content, such as the label, gradient view, etc. + * @param textResourceId The resource ID of the text to show on the label. + * @param seekBarMax The range of the seek bar. + * @param seekBarListener The listener for when the seek bar value changes. + */ + ColorPickerAdvancedComponent(final View rootView, + final int textResourceId, + final int seekBarMax, + final OnSeekBarChangeListener seekBarListener) { + mGradientView = rootView.findViewById(R.id.gradient); + mText = (TextView) rootView.findViewById(R.id.text); + mText.setText(textResourceId); + mGradientDrawable = new GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT, null); + mSeekBar = (SeekBar) rootView.findViewById(R.id.seek_bar); + mSeekBar.setOnSeekBarChangeListener(seekBarListener); + mSeekBar.setMax(seekBarMax); + // Setting the thumb offset means the seek bar thumb can move all the way to each end + // of the gradient view. + mSeekBar.setThumbOffset(mSeekBar.getThumb().getIntrinsicWidth() / 2); + } + + /** + * @return The value represented by this component, maintained by the seek bar progress. + */ + public float getValue() { + return mSeekBar.getProgress(); + } + + /** + * Sets the value of the component (by setting the seek bar value). + * + * @param newValue The value to give the component. + */ + public void setValue(float newValue) { + mSeekBar.setProgress((int) newValue); + } + + /** + * Sets the colors for the gradient view to interpolate through. + * + * @param newColors The set of colors representing the interpolation points for the gradient. + */ + public void setGradientColors(int[] newColors) { + mGradientColors = newColors.clone(); + mGradientDrawable.setColors(mGradientColors); + mGradientView.setBackground(mGradientDrawable); + } +} diff --git a/ui/android/java/src/org/chromium/ui/ColorPickerDialog.java b/ui/android/java/src/org/chromium/ui/ColorPickerDialog.java index d743a5c..58f1e0a 100644 --- a/ui/android/java/src/org/chromium/ui/ColorPickerDialog.java +++ b/ui/android/java/src/org/chromium/ui/ColorPickerDialog.java @@ -4,209 +4,163 @@ package org.chromium.ui; +import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.RectF; -import android.graphics.Shader; -import android.graphics.SweepGradient; -import android.os.Bundle; -import android.view.MotionEvent; +import android.view.LayoutInflater; import android.view.View; +import android.widget.Button; +import android.widget.TextView; /** - * UI for the color chooser that shows on the Android platform as a result - * of <input type=color > form element. - * - * <p> Note that this UI is only temporary and will be replaced once the UI - * design in - * https://code.google.com/p/chromium/issues/detail?id=162491 is finalized + * UI for the color chooser that shows on the Android platform as a result of + * <input type=color > form element. */ -public class ColorPickerDialog extends Dialog { +public class ColorPickerDialog extends AlertDialog implements OnColorChangedListener { + private final ColorPickerAdvanced mAdvancedColorPicker; - public interface OnColorChangedListener { - void colorChanged(int color); - } + private final ColorPickerSimple mSimpleColorPicker; - private OnColorChangedListener mListener; - private int mInitialColor; - - private static class ColorPickerView extends View { - private static final int CENTER_RADIUS = 32; - private static final int DIALOG_HEIGHT = 200; - private static final int BOUNDING_BOX_EDGE = 100; - private static final float PI = 3.1415926f; - - private final Paint mPaint; - private final Paint mCenterPaint; - private final int[] mColors; - private final OnColorChangedListener mListener; - private boolean mTrackingCenter; - private boolean mHighlightCenter; - - private int center_x = -1; - private int center_y = -1; - - ColorPickerView(Context c, OnColorChangedListener listener, int color) { - super(c); - mListener = listener; - mColors = new int[] { - 0xFFFF0000, 0xFFFF00FF, 0xFF0000FF, 0xFF00FFFF, 0xFF00FF00, - 0xFFFFFF00, 0xFFFF0000 - }; - Shader shader = new SweepGradient(0, 0, mColors, null); - - mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mPaint.setShader(shader); - mPaint.setStyle(Paint.Style.STROKE); - mPaint.setStrokeWidth(32); - - mCenterPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mCenterPaint.setColor(color); - mCenterPaint.setStrokeWidth(5); - } + private final Button mMoreButton; - @Override - protected void onDraw(Canvas canvas) { - if (center_x == -1) { - center_x = getWidth() / 2; - } - if (center_y == -1) { - center_y = getHeight() / 2; - } + // The view up in the corner that shows the user the color they've currently selected. + private final View mCurrentColorView; - float r = BOUNDING_BOX_EDGE - mPaint.getStrokeWidth() * 0.5f; + private final OnColorChangedListener mListener; - canvas.translate(center_x, center_y); + private final int mInitialColor; - canvas.drawOval(new RectF(-r, -r, r, r), mPaint); - canvas.drawCircle(0, 0, CENTER_RADIUS, mCenterPaint); + private int mCurrentColor; - if (mTrackingCenter) { - int color = mCenterPaint.getColor(); - mCenterPaint.setStyle(Paint.Style.STROKE); + /** + * @param context The context the dialog is to run in. + * @param theme The theme to display the dialog in. + * @param listener The object to notify when the color is set. + * @param color The initial color to set. + */ + public ColorPickerDialog(Context context, OnColorChangedListener listener, int color) { + super(context, 0); - if (mHighlightCenter) { - mCenterPaint.setAlpha(0xFF); - } else { - mCenterPaint.setAlpha(0x80); - } - canvas.drawCircle(0, 0, - CENTER_RADIUS + mCenterPaint.getStrokeWidth(), - mCenterPaint); + mListener = listener; + mInitialColor = color; + mCurrentColor = mInitialColor; + + // Initialize title + LayoutInflater inflater = (LayoutInflater) context + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + View title = inflater.inflate(R.layout.color_picker_dialog_title, null); + setCustomTitle(title); + + mCurrentColorView = title.findViewById(R.id.selected_color_view); + + TextView titleText = (TextView) title.findViewById(R.id.title); + titleText.setText(R.string.color_picker_dialog_title); + + // Initialize Set/Cancel buttons + String positiveButtonText = context.getString(R.string.color_picker_button_set); + setButton(BUTTON_POSITIVE, positiveButtonText, + new Dialog.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + tryNotifyColorSet(mCurrentColor); + } + }); + + // Note that with the color picker there's not really any such thing as + // "cancelled". + // The color picker flow only finishes when we return a color, so we + // have to always + // return something. The concept of "cancelled" in this case just means + // returning + // the color that we were initialized with. + String negativeButtonText = context.getString(R.string.color_picker_button_cancel); + setButton(BUTTON_NEGATIVE, negativeButtonText, + new Dialog.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + tryNotifyColorSet(mInitialColor); + } + }); - mCenterPaint.setStyle(Paint.Style.FILL); - mCenterPaint.setColor(color); + setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface arg0) { + tryNotifyColorSet(mInitialColor); } - } + }); - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - setMeasuredDimension(widthMeasureSpec, DIALOG_HEIGHT); - } - - private static int interpolate(int low, int high, float interPolant) { - return low + java.lang.Math.round(interPolant * (high - low)); - } + // Initialize main content view + View content = inflater.inflate(R.layout.color_picker_dialog_content, null); + setView(content); - static int interpolateColor(int colors[], float x, float y) { - float angle = (float)java.lang.Math.atan2(y, x); - float unit = angle / (2 * PI); - if (unit < 0) { - unit += 1; - } - if (unit <= 0) { - return colors[0]; - } - if (unit >= 1) { - return colors[colors.length - 1]; + // Initialize More button. + mMoreButton = (Button) content.findViewById(R.id.more_colors_button); + mMoreButton.setOnClickListener(new Button.OnClickListener() { + @Override + public void onClick(View v) { + showAdvancedView(); } + }); - 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 = interpolate(Color.alpha(c0), Color.alpha(c1), p); - int r = interpolate(Color.red(c0), Color.red(c1), p); - int g = interpolate(Color.green(c0), Color.green(c1), p); - int b = interpolate(Color.blue(c0), Color.blue(c1), p); + // Initialize advanced color view (hidden initially). + mAdvancedColorPicker = + (ColorPickerAdvanced) content.findViewById(R.id.color_picker_advanced); + mAdvancedColorPicker.setVisibility(View.GONE); - return Color.argb(a, r, g, b); - } + // Initialize simple color view (default view). + mSimpleColorPicker = (ColorPickerSimple) content.findViewById(R.id.color_picker_simple); + mSimpleColorPicker.init(this); - @Override - public boolean onTouchEvent(MotionEvent event) { - float x = event.getX() - center_x; - float y = event.getY() - center_y; - - // equivalent to sqrt(x * x + y * y) <= CENTER_RADIUS but cheaper - boolean inCenter = (x * x + y * y) <= (CENTER_RADIUS * CENTER_RADIUS); - - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - mTrackingCenter = inCenter; - if (inCenter) { - mHighlightCenter = true; - invalidate(); - break; - } - case MotionEvent.ACTION_MOVE: - if (mTrackingCenter) { - if (mHighlightCenter != inCenter) { - mHighlightCenter = inCenter; - invalidate(); - } - } else { - mCenterPaint.setColor(interpolateColor(mColors, x, y)); - invalidate(); - } - break; - case MotionEvent.ACTION_UP: - if (mTrackingCenter) { - if (inCenter) { - mListener.colorChanged(mCenterPaint.getColor()); - } - - // Draw without the halo surrounding the central circle. - mTrackingCenter = false; - invalidate(); - } - break; - default: - break; - } - return true; - } + updateCurrentColor(mInitialColor); } - public ColorPickerDialog(Context context, - OnColorChangedListener listener, - int initialColor) { - super(context); - - mListener = listener; - mInitialColor = initialColor; + /** + * Listens to the ColorPicker for when the user has changed the selected color, and + * updates the current color (the color shown in the title) accordingly. + * + * @param color The new color chosen by the user. + */ + @Override + public void onColorChanged(int color) { + updateCurrentColor(color); + } - setOnCancelListener(new DialogInterface.OnCancelListener() { - @Override - public void onCancel(DialogInterface arg0) { - mListener.colorChanged(mInitialColor); - } - }); + /** + * Hides the simple view (the default) and shows the advanced one instead, hiding the + * "More" button at the same time. + */ + private void showAdvancedView() { + // Only need to hide the borders, not the Views themselves, since the Views are + // contained within the borders. + View buttonBorder = findViewById(R.id.more_colors_button_border); + buttonBorder.setVisibility(View.GONE); + + View simpleViewBorder = findViewById(R.id.color_picker_simple_border); + simpleViewBorder.setVisibility(View.GONE); + + mAdvancedColorPicker.setVisibility(View.VISIBLE); + mAdvancedColorPicker.setListener(this); + mAdvancedColorPicker.setColor(mCurrentColor); } - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(new ColorPickerView(getContext(), mListener, mInitialColor)); + /** + * Tries to notify any listeners that the color has been set. + */ + private void tryNotifyColorSet(int color) { + if (mListener != null) { + mListener.onColorChanged(color); + } + } - // TODO(miguelg): Internationalization - setTitle("Select Color"); + /** + * Updates the internal cache of the currently selected color, updating the colorful little + * box in the title at the same time. + */ + private void updateCurrentColor(int color) { + mCurrentColor = color; + if (mCurrentColorView != null) { + mCurrentColorView.setBackgroundColor(color); + } } } diff --git a/ui/android/java/src/org/chromium/ui/ColorPickerSimple.java b/ui/android/java/src/org/chromium/ui/ColorPickerSimple.java new file mode 100644 index 0000000..5c0382e --- /dev/null +++ b/ui/android/java/src/org/chromium/ui/ColorPickerSimple.java @@ -0,0 +1,146 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +package org.chromium.ui; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; + + +/** + * Draws a grid of (predefined) colors and allows the user to choose one of + * those colors. + */ +public class ColorPickerSimple extends View { + private static final int ROW_COUNT = 2; + + private static final int COLUMN_COUNT = 4; + + private static final int GRID_CELL_COUNT = ROW_COUNT * COLUMN_COUNT; + + private static final int[] COLORS = { Color.RED, + Color.CYAN, + Color.BLUE, + Color.GREEN, + Color.MAGENTA, + Color.YELLOW, + Color.BLACK, + Color.WHITE + }; + + private Rect[] mBounds; + + private Paint[] mPaints; + + private OnColorChangedListener mOnColorTouchedListener; + + public ColorPickerSimple(Context context) { + super(context); + } + + public ColorPickerSimple(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public ColorPickerSimple(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + /** + * Initializes the listener and precalculates the grid and color positions. + * + * @param onColorChangedListener The listener that gets notified when the user touches + * a color. + */ + public void init(OnColorChangedListener onColorChangedListener) { + mOnColorTouchedListener = onColorChangedListener; + + // This will get calculated when the layout size is updated. + mBounds = null; + + mPaints = new Paint[GRID_CELL_COUNT]; + for (int i = 0; i < GRID_CELL_COUNT; ++i) { + Paint newPaint = new Paint(); + newPaint.setColor(COLORS[i]); + mPaints[i] = newPaint; + } + } + + /** + * Draws the grid of colors, based on the rectangles calculated in onSizeChanged(). + * + * @param canvas The canvas the colors are drawn onto. + */ + @Override + public void onDraw(Canvas canvas) { + if (mBounds == null || mPaints == null) { + return; + } + for (int i = 0; i < GRID_CELL_COUNT; ++i) { + canvas.drawRect(mBounds[i], mPaints[i]); + } + } + + /** + * Responds to the user touching the grid and works out which color has been chosen as + * a result, depending on the X,Y coordinate. + * + * @param event The MotionEvent the X,Y coordinates are retrieved from. + */ + @Override + public boolean onTouchEvent(MotionEvent event) { + if (event.getAction() != MotionEvent.ACTION_DOWN || + mOnColorTouchedListener == null) { + return false; + } + + if ((getWidth() > 0) && (getHeight() > 0)) { + int x = (int) event.getX(); + int y = (int) event.getY(); + + int column = x * COLUMN_COUNT / getWidth(); + int row = y * ROW_COUNT / getHeight(); + + int colorIndex = (row * COLUMN_COUNT) + column; + if (colorIndex >= 0 && colorIndex < COLORS.length) { + mOnColorTouchedListener.onColorChanged(COLORS[colorIndex]); + } + } + return true; + } + + /** + * Recalculates the color grid with the new sizes. + */ + @Override + protected void onSizeChanged(int width, int height, int oldw, int oldh) { + calculateGrid(width, height); + } + + /** + * Calculates the sizes and positions of the cells in the grid, splitting + * them up as evenly as possible. + */ + private void calculateGrid(final int width, final int height) { + mBounds = new Rect[GRID_CELL_COUNT]; + + for (int i = 0; i < ROW_COUNT; ++i) { + for (int j = 0; j < COLUMN_COUNT; ++j) { + int left = j * width / COLUMN_COUNT; + int right = (j + 1) * width / COLUMN_COUNT; + + int top = i * height / ROW_COUNT; + int bottom = (i + 1) * height / ROW_COUNT; + + Rect rect = new Rect(left, top, right, bottom); + mBounds[(i * COLUMN_COUNT) + j] = rect; + } + } + } +} diff --git a/ui/android/java/src/org/chromium/ui/OnColorChangedListener.java b/ui/android/java/src/org/chromium/ui/OnColorChangedListener.java new file mode 100644 index 0000000..4caa3cf --- /dev/null +++ b/ui/android/java/src/org/chromium/ui/OnColorChangedListener.java @@ -0,0 +1,17 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +package org.chromium.ui; + +/** + * The callback used to indicate the user changed the color. + */ +public interface OnColorChangedListener { + + /** + * Called upon a color change. + * + * @param color The color that was set. + */ + void onColorChanged(int color); +}
\ No newline at end of file diff --git a/ui/android/java/strings/android_ui_strings.grd b/ui/android/java/strings/android_ui_strings.grd index da7ffd0..da7eee2 100644 --- a/ui/android/java/strings/android_ui_strings.grd +++ b/ui/android/java/strings/android_ui_strings.grd @@ -8,6 +8,27 @@ <message desc="Toast when the browser is unable to open a file for upload. [CHAR-LIMIT=32]" name="IDS_OPENING_FILE_ERROR"> Failed to open selected file </message> + <message desc="Text for ColorPicker button to go to advanced view. [CHAR-LIMIT=20]" name="IDS_COLOR_PICKER_BUTTON_MORE"> + More + </message> + <message desc="Label for hue slider in ColorPicker." name="IDS_COLOR_PICKER_HUE"> + Hue + </message> + <message desc="Label for saturation slider in ColorPicker." name="IDS_COLOR_PICKER_SATURATION"> + Saturation + </message> + <message desc="Label for lightness slider in ColorPicker" name="IDS_COLOR_PICKER_LIGHTNESS"> + Lightness + </message> + <message desc="Label for button in ColorPicker dialog for user to accept the currently chosen color. [CHAR-LIMIT=20]" name="IDS_COLOR_PICKER_BUTTON_SET"> + Set + </message> + <message desc="Label for button in ColorPicker dialog for user to cancel picking a color. [CHAR-LIMIT=20]" name="IDS_COLOR_PICKER_BUTTON_CANCEL"> + Cancel + </message> + <message desc="Title of ColorPicker dialog. [CHAR-LIMIT=20]" name="IDS_COLOR_PICKER_DIALOG_TITLE"> + Select color + </message> </messages> </release> <translations> |