1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5  * except in compliance with the License. You may obtain a copy of the License at
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the
10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11  * KIND, either express or implied. See the License for the specific language governing
12  * permissions and limitations under the License.
13  */
14 
15 package com.android.systemui.qs;
16 
17 import static com.android.systemui.qs.tileimpl.QSIconViewImpl.QS_ANIM_LENGTH;
18 
19 import android.animation.ObjectAnimator;
20 import android.animation.ValueAnimator;
21 import android.annotation.ColorInt;
22 import android.annotation.IntRange;
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.content.res.ColorStateList;
26 import android.graphics.Canvas;
27 import android.graphics.ColorFilter;
28 import android.graphics.Matrix;
29 import android.graphics.Paint;
30 import android.graphics.Path;
31 import android.graphics.Path.Direction;
32 import android.graphics.PorterDuff.Mode;
33 import android.graphics.Rect;
34 import android.graphics.RectF;
35 import android.graphics.drawable.Drawable;
36 import android.util.FloatProperty;
37 
38 public class SlashDrawable extends Drawable {
39 
40     public static final float CORNER_RADIUS = 1f;
41 
42     private final Path mPath = new Path();
43     private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
44 
45     // These values are derived in un-rotated (vertical) orientation
46     private static final float SLASH_WIDTH = 1.8384776f;
47     private static final float SLASH_HEIGHT = 28f;
48     private static final float CENTER_X = 10.65f;
49     private static final float CENTER_Y = 11.869239f;
50     private static final float SCALE = 24f;
51 
52     // Bottom is derived during animation
53     private static final float LEFT = (CENTER_X - (SLASH_WIDTH / 2)) / SCALE;
54     private static final float TOP = (CENTER_Y - (SLASH_HEIGHT / 2)) / SCALE;
55     private static final float RIGHT = (CENTER_X + (SLASH_WIDTH / 2)) / SCALE;
56     // Draw the slash washington-monument style; rotate to no-u-turn style
57     private static final float DEFAULT_ROTATION = -45f;
58 
59     private Drawable mDrawable;
60     private final RectF mSlashRect = new RectF(0, 0, 0, 0);
61     private float mRotation;
62     private boolean mSlashed;
63     @Nullable
64     private Mode mTintMode;
65     @Nullable
66     private ColorStateList mTintList;
67     private boolean mAnimationEnabled = true;
68 
SlashDrawable(Drawable d)69     public SlashDrawable(Drawable d) {
70         mDrawable = d;
71     }
72 
73     @Override
getIntrinsicHeight()74     public int getIntrinsicHeight() {
75         return mDrawable != null ? mDrawable.getIntrinsicHeight(): 0;
76     }
77 
78     @Override
getIntrinsicWidth()79     public int getIntrinsicWidth() {
80         return mDrawable != null ? mDrawable.getIntrinsicWidth(): 0;
81     }
82 
83     @Override
onBoundsChange(Rect bounds)84     protected void onBoundsChange(Rect bounds) {
85         super.onBoundsChange(bounds);
86         mDrawable.setBounds(bounds);
87     }
88 
setDrawable(Drawable d)89     public void setDrawable(Drawable d) {
90         mDrawable = d;
91         mDrawable.setCallback(getCallback());
92         mDrawable.setBounds(getBounds());
93         if (mTintMode != null) mDrawable.setTintMode(mTintMode);
94         if (mTintList != null) mDrawable.setTintList(mTintList);
95         invalidateSelf();
96     }
97 
setRotation(float rotation)98     public void setRotation(float rotation) {
99         if (mRotation == rotation) return;
100         mRotation = rotation;
101         invalidateSelf();
102     }
103 
setAnimationEnabled(boolean enabled)104     public void setAnimationEnabled(boolean enabled) {
105         mAnimationEnabled = enabled;
106     }
107 
108     // Animate this value on change
109     private float mCurrentSlashLength;
110     private final FloatProperty mSlashLengthProp = new FloatProperty<SlashDrawable>("slashLength") {
111         @Override
112         public void setValue(SlashDrawable object, float value) {
113             object.mCurrentSlashLength = value;
114         }
115 
116         @Override
117         public Float get(SlashDrawable object) {
118             return object.mCurrentSlashLength;
119         }
120     };
121 
setSlashed(boolean slashed)122     public void setSlashed(boolean slashed) {
123         if (mSlashed == slashed) return;
124 
125         mSlashed = slashed;
126 
127         final float end = mSlashed ? SLASH_HEIGHT / SCALE : 0f;
128         final float start = mSlashed ? 0f : SLASH_HEIGHT / SCALE;
129 
130         if (mAnimationEnabled) {
131             ObjectAnimator anim = ObjectAnimator.ofFloat(this, mSlashLengthProp, start, end);
132             anim.addUpdateListener((ValueAnimator valueAnimator) -> invalidateSelf());
133             anim.setDuration(QS_ANIM_LENGTH);
134             anim.start();
135         } else {
136             mCurrentSlashLength = end;
137             invalidateSelf();
138         }
139     }
140 
141     @Override
draw(@onNull Canvas canvas)142     public void draw(@NonNull Canvas canvas) {
143         canvas.save();
144         Matrix m = new Matrix();
145         final int width = getBounds().width();
146         final int height = getBounds().height();
147         final float radiusX = scale(CORNER_RADIUS, width);
148         final float radiusY = scale(CORNER_RADIUS, height);
149         updateRect(
150                 scale(LEFT, width),
151                 scale(TOP, height),
152                 scale(RIGHT, width),
153                 scale(TOP + mCurrentSlashLength, height)
154         );
155 
156         mPath.reset();
157         // Draw the slash vertically
158         mPath.addRoundRect(mSlashRect, radiusX, radiusY, Direction.CW);
159         // Rotate -45 + desired rotation
160         m.setRotate(mRotation + DEFAULT_ROTATION, width / 2, height / 2);
161         mPath.transform(m);
162         canvas.drawPath(mPath, mPaint);
163 
164         // Rotate back to vertical
165         m.setRotate(-mRotation - DEFAULT_ROTATION, width / 2, height / 2);
166         mPath.transform(m);
167 
168         // Draw another rect right next to the first, for clipping
169         m.setTranslate(mSlashRect.width(), 0);
170         mPath.transform(m);
171         mPath.addRoundRect(mSlashRect, 1.0f * width, 1.0f * height, Direction.CW);
172         m.setRotate(mRotation + DEFAULT_ROTATION, width / 2, height / 2);
173         mPath.transform(m);
174         canvas.clipOutPath(mPath);
175 
176         mDrawable.draw(canvas);
177         canvas.restore();
178     }
179 
scale(float frac, int width)180     private float scale(float frac, int width) {
181         return frac * width;
182     }
183 
updateRect(float left, float top, float right, float bottom)184     private void updateRect(float left, float top, float right, float bottom) {
185         mSlashRect.left = left;
186         mSlashRect.top = top;
187         mSlashRect.right = right;
188         mSlashRect.bottom = bottom;
189     }
190 
191     @Override
setTint(@olorInt int tintColor)192     public void setTint(@ColorInt int tintColor) {
193         super.setTint(tintColor);
194         mDrawable.setTint(tintColor);
195         mPaint.setColor(tintColor);
196     }
197 
198     @Override
setTintList(@ullable ColorStateList tint)199     public void setTintList(@Nullable ColorStateList tint) {
200         mTintList = tint;
201         super.setTintList(tint);
202         setDrawableTintList(tint);
203         mPaint.setColor(tint.getDefaultColor());
204         invalidateSelf();
205     }
206 
setDrawableTintList(@ullable ColorStateList tint)207     protected void setDrawableTintList(@Nullable ColorStateList tint) {
208         mDrawable.setTintList(tint);
209     }
210 
211     @Override
setTintMode(@onNull Mode tintMode)212     public void setTintMode(@NonNull Mode tintMode) {
213         mTintMode = tintMode;
214         super.setTintMode(tintMode);
215         mDrawable.setTintMode(tintMode);
216     }
217 
218     @Override
setAlpha(@ntRangefrom = 0, to = 255) int alpha)219     public void setAlpha(@IntRange(from = 0, to = 255) int alpha) {
220         mDrawable.setAlpha(alpha);
221         mPaint.setAlpha(alpha);
222     }
223 
224     @Override
setColorFilter(@ullable ColorFilter colorFilter)225     public void setColorFilter(@Nullable ColorFilter colorFilter) {
226         mDrawable.setColorFilter(colorFilter);
227         mPaint.setColorFilter(colorFilter);
228     }
229 
230     @Override
getOpacity()231     public int getOpacity() {
232         return 255;
233     }
234 }
235