• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.internal.widget.remotecompose.core.operations.layout.modifiers;
17 
18 import android.annotation.NonNull;
19 
20 import com.android.internal.widget.remotecompose.core.CoreDocument;
21 import com.android.internal.widget.remotecompose.core.Operation;
22 import com.android.internal.widget.remotecompose.core.Operations;
23 import com.android.internal.widget.remotecompose.core.PaintContext;
24 import com.android.internal.widget.remotecompose.core.RemoteContext;
25 import com.android.internal.widget.remotecompose.core.WireBuffer;
26 import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
27 import com.android.internal.widget.remotecompose.core.operations.Utils;
28 import com.android.internal.widget.remotecompose.core.operations.layout.Component;
29 import com.android.internal.widget.remotecompose.core.operations.layout.RootLayoutComponent;
30 import com.android.internal.widget.remotecompose.core.operations.layout.TouchHandler;
31 import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
32 import com.android.internal.widget.remotecompose.core.operations.utilities.ColorUtils;
33 import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
34 import com.android.internal.widget.remotecompose.core.operations.utilities.easing.Easing;
35 import com.android.internal.widget.remotecompose.core.operations.utilities.easing.FloatAnimation;
36 import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
37 import com.android.internal.widget.remotecompose.core.serialize.SerializeTags;
38 
39 import java.util.List;
40 
41 /** Represents a ripple effect */
42 public class RippleModifierOperation extends DecoratorModifierOperation implements TouchHandler {
43     private static final int OP_CODE = Operations.MODIFIER_RIPPLE;
44 
45     long mAnimateRippleStart = 0;
46     float mAnimateRippleX = 0f;
47     float mAnimateRippleY = 0f;
48     int mAnimateRippleDuration = 1000;
49 
50     float mWidth = 0;
51     float mHeight = 0;
52 
53     @NonNull public float[] locationInWindow = new float[2];
54 
55     @NonNull PaintBundle mPaint = new PaintBundle();
56 
57     /**
58      * Animate the ripple effect
59      *
60      * @param x
61      * @param y
62      */
animateRipple(float x, float y)63     public void animateRipple(float x, float y) {
64         mAnimateRippleStart = System.currentTimeMillis();
65         mAnimateRippleX = x;
66         mAnimateRippleY = y;
67     }
68 
69     @Override
write(@onNull WireBuffer buffer)70     public void write(@NonNull WireBuffer buffer) {
71         apply(buffer);
72     }
73 
74     @NonNull
75     @Override
toString()76     public String toString() {
77         return "RippleModifier";
78     }
79 
80     @Override
apply(@onNull RemoteContext context)81     public void apply(@NonNull RemoteContext context) {
82         RootLayoutComponent root = context.getDocument().getRootLayoutComponent();
83         if (root != null) {
84             root.setHasTouchListeners(true);
85         }
86     }
87 
88     @NonNull
89     @Override
deepToString(@onNull String indent)90     public String deepToString(@NonNull String indent) {
91         return (indent != null ? indent : "") + toString();
92     }
93 
94     @Override
paint(@onNull PaintContext context)95     public void paint(@NonNull PaintContext context) {
96         if (mAnimateRippleStart == 0) {
97             return;
98         }
99         context.needsRepaint();
100 
101         float progress = (System.currentTimeMillis() - mAnimateRippleStart);
102         progress /= (float) mAnimateRippleDuration;
103         if (progress > 1f) {
104             mAnimateRippleStart = 0;
105         }
106         progress = Math.min(1f, progress);
107         context.save();
108         context.savePaint();
109         mPaint.reset();
110 
111         FloatAnimation anim1 =
112                 new FloatAnimation(Easing.CUBIC_STANDARD, 1f, null, Float.NaN, Float.NaN);
113         anim1.setInitialValue(0f);
114         anim1.setTargetValue(1f);
115         float tween = anim1.get(progress);
116 
117         FloatAnimation anim2 =
118                 new FloatAnimation(Easing.CUBIC_STANDARD, 0.5f, null, Float.NaN, Float.NaN);
119         anim2.setInitialValue(0f);
120         anim2.setTargetValue(1f);
121         float tweenRadius = anim2.get(progress);
122 
123         int startColor = ColorUtils.createColor(250, 250, 250, 180);
124         int endColor = ColorUtils.createColor(200, 200, 200, 0);
125         int paintedColor = Utils.interpolateColor(startColor, endColor, tween);
126 
127         float radius = Math.max(mWidth, mHeight) * tweenRadius;
128         mPaint.setColor(paintedColor);
129         context.replacePaint(mPaint);
130         context.clipRect(0f, 0f, mWidth, mHeight);
131         context.drawCircle(mAnimateRippleX, mAnimateRippleY, radius);
132         context.restorePaint();
133         context.restore();
134     }
135 
136     @Override
layout( @onNull RemoteContext context, Component component, float width, float height)137     public void layout(
138             @NonNull RemoteContext context, Component component, float width, float height) {
139         mWidth = width;
140         mHeight = height;
141     }
142 
143     @Override
serializeToString(int indent, @NonNull StringSerializer serializer)144     public void serializeToString(int indent, @NonNull StringSerializer serializer) {
145         serializer.append(indent, "RIPPLE_MODIFIER");
146     }
147 
148     /**
149      * The operation name
150      *
151      * @return operation name
152      */
153     @NonNull
name()154     public static String name() {
155         return "RippleModifier";
156     }
157 
158     /**
159      * Write the operation to the buffer
160      *
161      * @param buffer a WireBuffer
162      */
apply(@onNull WireBuffer buffer)163     public static void apply(@NonNull WireBuffer buffer) {
164         buffer.start(OP_CODE);
165     }
166 
167     /**
168      * Read this operation and add it to the list of operations
169      *
170      * @param buffer the buffer to read
171      * @param operations the list of operations that will be added to
172      */
read(@onNull WireBuffer buffer, @NonNull List<Operation> operations)173     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
174         operations.add(new RippleModifierOperation());
175     }
176 
177     /**
178      * Populate the documentation with a description of this operation
179      *
180      * @param doc to append the description to.
181      */
documentation(@onNull DocumentationBuilder doc)182     public static void documentation(@NonNull DocumentationBuilder doc) {
183         doc.operation("Layout Operations", OP_CODE, name())
184                 .description(
185                         "Ripple modifier. This modifier will do a ripple animation on touch down");
186     }
187 
188     @Override
onTouchDown( RemoteContext context, CoreDocument document, Component component, float x, float y)189     public void onTouchDown(
190             RemoteContext context, CoreDocument document, Component component, float x, float y) {
191         locationInWindow[0] = 0f;
192         locationInWindow[1] = 0f;
193         component.getLocationInWindow(locationInWindow);
194         animateRipple(x - locationInWindow[0], y - locationInWindow[1]);
195         context.hapticEffect(3);
196     }
197 
198     @Override
onTouchUp( RemoteContext context, CoreDocument document, Component component, float x, float y, float dx, float dy)199     public void onTouchUp(
200             RemoteContext context,
201             CoreDocument document,
202             Component component,
203             float x,
204             float y,
205             float dx,
206             float dy) {}
207 
208     @Override
onTouchDrag( RemoteContext context, CoreDocument document, Component component, float x, float y)209     public void onTouchDrag(
210             RemoteContext context, CoreDocument document, Component component, float x, float y) {}
211 
212     @Override
onTouchCancel( RemoteContext context, CoreDocument document, Component component, float x, float y)213     public void onTouchCancel(
214             RemoteContext context, CoreDocument document, Component component, float x, float y) {}
215 
216     @Override
serialize(MapSerializer serializer)217     public void serialize(MapSerializer serializer) {
218         serializer
219                 .addTags(SerializeTags.MODIFIER)
220                 .addType("RippleModifierOperation")
221                 .add("animateRippleStart", mAnimateRippleStart)
222                 .add("animateRippleX", mAnimateRippleX)
223                 .add("animateRippleY", mAnimateRippleY)
224                 .add("animateRippleDuration", mAnimateRippleDuration)
225                 .add("width", mWidth)
226                 .add("height", mHeight);
227     }
228 }
229