1 package org.coolreader.crengine;
2 
3 // based on color picker from
4 // http://www.anddev.org/announce_color_picker_dialog-t10771.html
5 
6 import org.coolreader.R;
7 
8 import android.content.res.Resources;
9 import android.content.res.Resources.NotFoundException;
10 import android.graphics.Bitmap;
11 import android.graphics.Bitmap.Config;
12 import android.graphics.BitmapFactory;
13 import android.graphics.BlurMaskFilter;
14 import android.graphics.BlurMaskFilter.Blur;
15 import android.graphics.Canvas;
16 import android.graphics.Color;
17 import android.graphics.ColorFilter;
18 import android.graphics.Paint;
19 import android.graphics.Paint.Style;
20 import android.graphics.PixelFormat;
21 import android.graphics.PorterDuff.Mode;
22 import android.graphics.Rect;
23 import android.graphics.Typeface;
24 import android.graphics.drawable.Drawable;
25 import android.graphics.drawable.GradientDrawable;
26 import android.graphics.drawable.LayerDrawable;
27 import android.os.SystemClock;
28 import android.util.StateSet;
29 import android.view.LayoutInflater;
30 import android.view.View;
31 import android.view.animation.Animation;
32 import android.view.animation.AnimationUtils;
33 import android.view.animation.DecelerateInterpolator;
34 import android.view.animation.Transformation;
35 import android.widget.SeekBar;
36 import android.widget.SeekBar.OnSeekBarChangeListener;
37 import android.widget.TextView;
38 
39 public class ColorPickerDialog extends BaseDialog implements OnSeekBarChangeListener {
40 
41     public interface OnColorChangedListener {
colorChanged(int color)42         public void colorChanged(int color);
43     }
44 
45 	private SeekBar mR;
46 	private SeekBar mG;
47 	private SeekBar mB;
48 	private SeekBar mHue;
49 	private SeekBar mSaturation;
50 	private SeekBar mValue;
51 	private TextView mLabel;
52 	private OnColorChangedListener mListener;
53 	private int mColor;
54 	private GradientDrawable mPreviewDrawable;
55 
ColorPickerDialog(BaseActivity activity, OnColorChangedListener listener, int color, String title)56 	public ColorPickerDialog(BaseActivity activity, OnColorChangedListener listener, int color, String title) {
57 		super(activity, title, false, true);
58 		mListener = listener;
59 
60 		Resources res = activity.getResources();
61 		setTitle(title);
62 		View root = LayoutInflater.from(activity).inflate(R.layout.color_picker, null);
63 		setView(root);
64 
65 		View preview = root.findViewById(R.id.preview);
66 		mPreviewDrawable = new GradientDrawable();
67 		// 2 pix more than color_picker_frame's radius
68 		mPreviewDrawable.setCornerRadius(7);
69 		Drawable[] layers;
70 		layers = new Drawable[] {
71 				mPreviewDrawable,
72 				res.getDrawable(R.drawable.color_picker_frame),
73 		};
74 		preview.setBackgroundDrawable(new LayerDrawable(layers));
75 
76 		mR = (SeekBar) root.findViewById(R.id.r);
77 		mG = (SeekBar) root.findViewById(R.id.g);
78 		mB = (SeekBar) root.findViewById(R.id.b);
79 		mHue = (SeekBar) root.findViewById(R.id.hue);
80 		mSaturation = (SeekBar) root.findViewById(R.id.saturation);
81 		mValue = (SeekBar) root.findViewById(R.id.value);
82 		mLabel = (TextView) root.findViewById(R.id.value_label);
83 
84 		mColor = color;
85 		int r = Color.red(mColor);
86 		int g = Color.green(mColor);
87 		int b = Color.blue(mColor);
88 		float[] hsv = new float[3];
89 		Color.colorToHSV(color, hsv);
90 		int h = (int) (hsv[0] * mHue.getMax() / 360);
91 		int s = (int) (hsv[1] * mSaturation.getMax());
92 		int v = (int) (hsv[2] * mValue.getMax());
93 		setupSeekBar(mR, R.string.options_color_r, r, res);
94 		setupSeekBar(mG, R.string.options_color_g, g, res);
95 		setupSeekBar(mB, R.string.options_color_b, b, res);
96 		setupSeekBar(mHue, R.string.options_color_hue, h, res);
97 		setupSeekBar(mSaturation, R.string.options_color_saturation, s, res);
98 		setupSeekBar(mValue, R.string.options_color_brightness, v, res);
99 
100 		updatePreview(color);
101 	}
102 
setupSeekBar(SeekBar seekBar, int id, int value, Resources res)103 	private void setupSeekBar(SeekBar seekBar, int id, int value, Resources res) {
104 		seekBar.setProgressDrawable(new TextSeekBarDrawable(res, id, value < seekBar.getMax() / 2));
105 		seekBar.setProgress(value);
106 		seekBar.setOnSeekBarChangeListener(this);
107 	}
108 
updateHSV()109 	private void updateHSV() {
110 		float[] hsv = {
111 			360 * mHue.getProgress() / (float) mHue.getMax(),
112 			mSaturation.getProgress() / (float) mSaturation.getMax(),
113 			mValue.getProgress() / (float) mValue.getMax(),
114 		};
115 		mColor = Color.HSVToColor(hsv);
116 		mR.setProgress(Color.red(mColor));
117 		mG.setProgress(Color.green(mColor));
118 		mB.setProgress(Color.blue(mColor));
119 		updatePreview(mColor);
120 	}
121 
updateRGB()122 	private void updateRGB() {
123 		mColor = Color.rgb(mR.getProgress(), mG.getProgress(), mB.getProgress());
124 		float[] hsv = new float[3];
125 		Color.colorToHSV(mColor, hsv);
126 		int h = (int) (hsv[0] * mHue.getMax() / 360);
127 		int s = (int) (hsv[1] * mSaturation.getMax());
128 		int v = (int) (hsv[2] * mValue.getMax());
129 		mHue.setProgress(h);
130 		mSaturation.setProgress(s);
131 		mValue.setProgress(v);
132 		updatePreview(mColor);
133 	}
134 
byteToHex(int n)135 	private static String byteToHex(int n) {
136 		String s = Integer.toHexString(n & 255);
137 		if (s.length()<2)
138 			s = "0" + s;
139 		return s;
140 	}
colorToHex(int n)141 	private static String colorToHex(int n) {
142 		return ("#" + byteToHex(Color.red(n))
143 			 + byteToHex(Color.green(n))
144 			 + byteToHex(Color.blue(n))).toUpperCase();
145 	}
updatePreview(int color)146 	private void updatePreview(int color) {
147 		mPreviewDrawable.setColor(color);
148 		mPreviewDrawable.invalidateSelf();
149 		mLabel.setText(colorToHex(mColor));
150 	}
151 
onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)152 	public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
153 		if ( fromUser ) {
154 			if ( seekBar==mR || seekBar==mG || seekBar==mB )
155 				updateRGB();
156 			else
157 				updateHSV();
158 		}
159 	}
160 
onStartTrackingTouch(SeekBar seekBar)161 	public void onStartTrackingTouch(SeekBar seekBar) {
162 	}
163 
onStopTrackingTouch(SeekBar seekBar)164 	public void onStopTrackingTouch(SeekBar seekBar) {
165 	}
166 
167 	@Override
onPositiveButtonClick()168 	protected void onPositiveButtonClick() {
169 		mListener.colorChanged(mColor);
170 		super.onPositiveButtonClick();
171 	}
172 
173 	@Override
onNegativeButtonClick()174 	protected void onNegativeButtonClick() {
175 		mListener.colorChanged(mColor);
176 		super.onPositiveButtonClick();
177 	}
178 
179 //	@Override
180 //	protected void onNegativeButtonClick() {
181 //		onPositiveButtonClick();
182 //	}
183 
184 	static class IconPreviewDrawable extends Drawable {
185 		private Bitmap mBitmap;
186 		private Bitmap mTmpBitmap;
187 		private Canvas mTmpCanvas;
188 		private int mTintColor;
189 
190 
IconPreviewDrawable(Resources res, int id)191 		public IconPreviewDrawable(Resources res, int id) {
192 			Bitmap b;
193 			try {
194 				b = BitmapFactory.decodeResource(res, id);
195 				if (b == null) {
196 					b = BitmapFactory.decodeResource(res, R.drawable.color_picker_icon);
197 				}
198 			} catch (NotFoundException e) {
199 				b = BitmapFactory.decodeResource(res, R.drawable.color_picker_icon);
200 			}
201 			mBitmap = b;
202 			mTmpBitmap = Bitmap.createBitmap(b.getWidth(), b.getHeight(), Config.ARGB_8888);
203 			mTmpCanvas = new Canvas(mTmpBitmap);
204 		}
205 
206 		@Override
draw(Canvas canvas)207 		public void draw(Canvas canvas) {
208 			Rect b = getBounds();
209 			float x = (b.width() - mBitmap.getWidth()) / 2.0f;
210 			float y = 0.75f * b.height() - mBitmap.getHeight() / 2.0f;
211 
212 			mTmpCanvas.drawColor(0, Mode.CLEAR);
213 			mTmpCanvas.drawBitmap(mBitmap, 0, 0, null);
214 			mTmpCanvas.drawColor(mTintColor, Mode.SRC_ATOP);
215 			canvas.drawBitmap(mTmpBitmap, x, y, null);
216 		}
217 
218 		@Override
getOpacity()219 		public int getOpacity() {
220 			return PixelFormat.TRANSLUCENT;
221 		}
222 
223 		@Override
setAlpha(int alpha)224 		public void setAlpha(int alpha) {
225 		}
226 
227 		@Override
setColorFilter(ColorFilter cf)228 		public void setColorFilter(ColorFilter cf) {
229 		}
230 
231 		@Override
setColorFilter(int color, Mode mode)232 		public void setColorFilter(int color, Mode mode) {
233 			mTintColor = color;
234 		}
235 	}
236 
237 	static final int[] STATE_FOCUSED = {android.R.attr.state_focused};
238 	static final int[] STATE_PRESSED = {android.R.attr.state_pressed};
239 
240 	static class TextSeekBarDrawable extends Drawable implements Runnable {
241 
242 		private static final String TAG = "TextSeekBarDrawable";
243 		private static final long DELAY = 50;
244 		private String mText;
245 		private Drawable mProgress;
246 		private Paint mPaint;
247 		private Paint mOutlinePaint;
248 		private float mTextWidth;
249 		private boolean mActive;
250 		private float mTextXScale;
251 		private int mDelta;
252 		private ScrollAnimation mAnimation;
253 
TextSeekBarDrawable(Resources res, int id, boolean labelOnRight)254 		public TextSeekBarDrawable(Resources res, int id, boolean labelOnRight) {
255 			mText = res.getString(id);
256 			mProgress = res.getDrawable(android.R.drawable.progress_horizontal);
257 			mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
258 			mPaint.setTypeface(Typeface.DEFAULT_BOLD);
259 			mPaint.setTextSize(16);
260 			mPaint.setColor(0xff000000);
261 			mOutlinePaint = new Paint(mPaint);
262 			mOutlinePaint.setStyle(Style.STROKE);
263 			mOutlinePaint.setStrokeWidth(3);
264 			mOutlinePaint.setColor(0xbbffc300);
265 			mOutlinePaint.setMaskFilter(new BlurMaskFilter(1, Blur.NORMAL));
266 			mTextWidth = mOutlinePaint.measureText(mText);
267 			mTextXScale = labelOnRight? 1 : 0;
268 			mAnimation = new ScrollAnimation();
269 		}
270 
271 		@Override
onBoundsChange(Rect bounds)272 		protected void onBoundsChange(Rect bounds) {
273 			mProgress.setBounds(bounds);
274 		}
275 
276 		@Override
onStateChange(int[] state)277 		protected boolean onStateChange(int[] state) {
278 			mActive = StateSet.stateSetMatches(STATE_FOCUSED, state) | StateSet.stateSetMatches(STATE_PRESSED, state);
279 			invalidateSelf();
280 			return false;
281 		}
282 
283 		@Override
isStateful()284 		public boolean isStateful() {
285 			return true;
286 		}
287 
288 		@Override
onLevelChange(int level)289 		protected boolean onLevelChange(int level) {
290 //			Log.d(TAG, "onLevelChange " + level);
291 			if (level < 4000 && mDelta <= 0) {
292 //				Log.d(TAG, "onLevelChange scheduleSelf ++");
293 				mDelta = 1;
294 				mAnimation.startScrolling(mTextXScale, 1);
295 				scheduleSelf(this, SystemClock.uptimeMillis() + DELAY);
296 			} else
297 			if (level > 6000 && mDelta >= 0) {
298 //				Log.d(TAG, "onLevelChange scheduleSelf --");
299 				mDelta = -1;
300 				mAnimation.startScrolling(mTextXScale, 0);
301 				scheduleSelf(this, SystemClock.uptimeMillis() + DELAY);
302 			}
303 			return mProgress.setLevel(level);
304 		}
305 
306 		@Override
draw(Canvas canvas)307 		public void draw(Canvas canvas) {
308 			mProgress.draw(canvas);
309 
310 			if (mAnimation.hasStarted() && !mAnimation.hasEnded()) {
311 				// pending animation
312 				mAnimation.getTransformation(AnimationUtils.currentAnimationTimeMillis(), null);
313 				mTextXScale = mAnimation.getCurrent();
314 //				Log.d(TAG, "draw " + mTextX + " " + SystemClock.uptimeMillis());
315 			}
316 
317 			Rect bounds = getBounds();
318 			float x = 6 + mTextXScale * (bounds.width() - mTextWidth - 6 - 6);
319 			float y = (bounds.height() + mPaint.getTextSize()) / 2;
320 			mOutlinePaint.setAlpha(mActive? 255 : 255 / 2);
321 			mPaint.setAlpha(mActive? 255 : 255 / 2);
322 			canvas.drawText(mText, x, y, mOutlinePaint);
323 			canvas.drawText(mText, x, y, mPaint);
324 		}
325 
326 		@Override
getOpacity()327 		public int getOpacity() {
328 			return PixelFormat.TRANSLUCENT;
329 		}
330 
331 		@Override
setAlpha(int alpha)332 		public void setAlpha(int alpha) {
333 		}
334 
335 		@Override
setColorFilter(ColorFilter cf)336 		public void setColorFilter(ColorFilter cf) {
337 		}
338 
run()339 		public void run() {
340 			mAnimation.getTransformation(AnimationUtils.currentAnimationTimeMillis(), null);
341 			// close interpolation of mTextX
342 			mTextXScale = mAnimation.getCurrent();
343 			if (!mAnimation.hasEnded()) {
344 				scheduleSelf(this, SystemClock.uptimeMillis() + DELAY);
345 			}
346 			invalidateSelf();
347 //			Log.d(TAG, "run " + mTextX + " " + SystemClock.uptimeMillis());
348 		}
349 	}
350 
351 	static class ScrollAnimation extends Animation {
352 		private static final String TAG = "ScrollAnimation";
353 		private static final long DURATION = 750;
354 		private float mFrom;
355 		private float mTo;
356 		private float mCurrent;
357 
ScrollAnimation()358 		public ScrollAnimation() {
359 			setDuration(DURATION);
360 			setInterpolator(new DecelerateInterpolator());
361 		}
362 
startScrolling(float from, float to)363 		public void startScrolling(float from, float to) {
364 			mFrom = from;
365 			mTo = to;
366 			startNow();
367 		}
368 
369 		@Override
applyTransformation(float interpolatedTime, Transformation t)370 		protected void applyTransformation(float interpolatedTime, Transformation t) {
371 			mCurrent = mFrom + (mTo - mFrom) * interpolatedTime;
372 //			Log.d(TAG, "applyTransformation " + mCurrent);
373 		}
374 
getCurrent()375 		public float getCurrent() {
376 			return mCurrent;
377 		}
378 	}
379 }
380