1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package org.chromium.ui;
6
7import android.content.Context;
8import android.graphics.Color;
9import android.util.AttributeSet;
10import android.view.LayoutInflater;
11import android.view.View;
12import android.view.accessibility.AccessibilityEvent;
13import android.widget.LinearLayout;
14import android.widget.SeekBar;
15import android.widget.SeekBar.OnSeekBarChangeListener;
16
17/**
18 * Represents a more advanced way for the user to choose a color, based on selecting each of
19 * the Hue, Saturation and Value attributes.
20 */
21public class ColorPickerAdvanced extends LinearLayout implements OnSeekBarChangeListener {
22    private static final int HUE_SEEK_BAR_MAX = 360;
23
24    private static final int HUE_COLOR_COUNT = 7;
25
26    private static final int SATURATION_SEEK_BAR_MAX = 100;
27
28    private static final int SATURATION_COLOR_COUNT = 2;
29
30    private static final int VALUE_SEEK_BAR_MAX = 100;
31
32    private static final int VALUE_COLOR_COUNT = 2;
33
34    ColorPickerAdvancedComponent mHueDetails;
35
36    ColorPickerAdvancedComponent mSaturationDetails;
37
38    ColorPickerAdvancedComponent mValueDetails;
39
40    private OnColorChangedListener mOnColorChangedListener;
41
42    private int mCurrentColor;
43
44    private final float[] mCurrentHsvValues = new float[3];
45
46    public ColorPickerAdvanced(Context context, AttributeSet attrs) {
47        super(context, attrs);
48        init();
49    }
50
51    public ColorPickerAdvanced(Context context, AttributeSet attrs, int defStyle) {
52        super(context, attrs, defStyle);
53        init();
54    }
55
56    public ColorPickerAdvanced(Context context) {
57        super(context);
58        init();
59    }
60
61    /**
62     * Initializes all the views and variables in the advanced view.
63     */
64    private void init() {
65        setOrientation(LinearLayout.VERTICAL);
66
67        mHueDetails = createAndAddNewGradient(R.string.color_picker_hue,
68                HUE_SEEK_BAR_MAX, this);
69        mSaturationDetails = createAndAddNewGradient(R.string.color_picker_saturation,
70                SATURATION_SEEK_BAR_MAX, this);
71        mValueDetails = createAndAddNewGradient(R.string.color_picker_value,
72                VALUE_SEEK_BAR_MAX, this);
73
74        refreshGradientComponents();
75    }
76
77    /**
78     * Creates a new GradientDetails object from the parameters provided, initializes it,
79     * and adds it to this advanced view.
80     *
81     * @param textResourceId The text to display for the label.
82     * @param seekBarMax The maximum value of the seek bar for the gradient.
83     * @param seekBarListener Object listening to when the user changes the seek bar.
84     *
85     * @return A new GradientDetails object initialized with the given parameters.
86     */
87    public ColorPickerAdvancedComponent createAndAddNewGradient(int textResourceId,
88            int seekBarMax,
89            OnSeekBarChangeListener seekBarListener) {
90        LayoutInflater inflater = (LayoutInflater) getContext()
91                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
92        View newComponent = inflater.inflate(R.layout.color_picker_advanced_component, null);
93        addView(newComponent);
94
95        return new ColorPickerAdvancedComponent(newComponent,
96                textResourceId,
97                seekBarMax,
98                seekBarListener);
99    }
100
101    /**
102     * Sets the listener for when the user changes the color.
103     *
104     * @param onColorChangedListener The object listening for the change in color.
105     */
106    public void setListener(OnColorChangedListener onColorChangedListener) {
107        mOnColorChangedListener = onColorChangedListener;
108    }
109
110    /**
111     * @return The color the user has currently chosen.
112     */
113    public int getColor() {
114        return mCurrentColor;
115    }
116
117    /**
118     * Sets the color that the user has currently chosen.
119     *
120     * @param color The currently chosen color.
121     */
122    public void setColor(int color) {
123        mCurrentColor = color;
124        Color.colorToHSV(mCurrentColor, mCurrentHsvValues);
125        refreshGradientComponents();
126    }
127
128    /**
129     * Notifies the listener, if there is one, of a change in the selected color.
130     */
131    private void notifyColorChanged() {
132        if (mOnColorChangedListener != null) {
133            mOnColorChangedListener.onColorChanged(getColor());
134        }
135    }
136
137    /**
138     * Callback for when a slider is updated on the advanced view.
139     *
140     * @param seekBar The color slider that was updated.
141     * @param progress The new value of the color slider.
142     * @param fromUser Whether it was the user the changed the value, or whether
143     *            we were setting it up.
144     */
145    @Override
146    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
147        if (fromUser) {
148            mCurrentHsvValues[0] = mHueDetails.getValue();
149            mCurrentHsvValues[1] = mSaturationDetails.getValue() / 100.0f;
150            mCurrentHsvValues[2] = mValueDetails.getValue() / 100.0f;
151
152            mCurrentColor = Color.HSVToColor(mCurrentHsvValues);
153
154            updateHueGradient();
155            updateSaturationGradient();
156            updateValueGradient();
157
158            notifyColorChanged();
159        }
160    }
161
162    /**
163     * Updates only the hue gradient display with the hue value for the
164     * currently selected color.
165     */
166    private void updateHueGradient() {
167        float[] tempHsvValues = new float[3];
168        tempHsvValues[1] = mCurrentHsvValues[1];
169        tempHsvValues[2] = mCurrentHsvValues[2];
170
171        int[] newColors = new int[HUE_COLOR_COUNT];
172
173        for (int i = 0; i < HUE_COLOR_COUNT; ++i) {
174            tempHsvValues[0] = i * 60.0f;
175            newColors[i] = Color.HSVToColor(tempHsvValues);
176        }
177        mHueDetails.setGradientColors(newColors);
178    }
179
180    /**
181     * Updates only the saturation gradient display with the saturation value
182     * for the currently selected color.
183     */
184    private void updateSaturationGradient() {
185        float[] tempHsvValues = new float[3];
186        tempHsvValues[0] = mCurrentHsvValues[0];
187        tempHsvValues[1] = 0.0f;
188        tempHsvValues[2] = mCurrentHsvValues[2];
189
190        int[] newColors = new int[SATURATION_COLOR_COUNT];
191
192        newColors[0] = Color.HSVToColor(tempHsvValues);
193
194        tempHsvValues[1] = 1.0f;
195        newColors[1] = Color.HSVToColor(tempHsvValues);
196        mSaturationDetails.setGradientColors(newColors);
197    }
198
199    /**
200     * Updates only the Value gradient display with the Value amount for
201     * the currently selected color.
202     */
203    private void updateValueGradient() {
204        float[] tempHsvValues = new float[3];
205        tempHsvValues[0] = mCurrentHsvValues[0];
206        tempHsvValues[1] = mCurrentHsvValues[1];
207        tempHsvValues[2] = 0.0f;
208
209        int[] newColors = new int[VALUE_COLOR_COUNT];
210
211        newColors[0] = Color.HSVToColor(tempHsvValues);
212
213        tempHsvValues[2] = 1.0f;
214        newColors[1] = Color.HSVToColor(tempHsvValues);
215        mValueDetails.setGradientColors(newColors);
216    }
217
218    /**
219     * Updates all the gradient displays to show the currently selected color.
220     */
221    private void refreshGradientComponents() {
222        // Round and bound the saturation value.
223        int saturationValue = Math.round(mCurrentHsvValues[1] * 100.0f);
224        saturationValue = Math.min(saturationValue, SATURATION_SEEK_BAR_MAX);
225        saturationValue = Math.max(saturationValue, 0);
226
227        // Round and bound the Value amount.
228        int valueValue = Math.round(mCurrentHsvValues[2] * 100.0f);
229        valueValue = Math.min(valueValue, VALUE_SEEK_BAR_MAX);
230        valueValue = Math.max(valueValue, 0);
231
232        // Don't need to round the hue value since its possible values match the seek bar
233        // range directly.
234        mHueDetails.setValue(mCurrentHsvValues[0]);
235        mSaturationDetails.setValue(saturationValue);
236        mValueDetails.setValue(valueValue);
237
238        updateHueGradient();
239        updateSaturationGradient();
240        updateValueGradient();
241    }
242
243    @Override
244    public void onStartTrackingTouch(SeekBar seekBar) {
245        // Do nothing.
246    }
247
248    @Override
249    public void onStopTrackingTouch(SeekBar seekBar) {
250        // Do nothing.
251    }
252}
253