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