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