• 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;
17 
18 import android.annotation.NonNull;
19 import android.annotation.Nullable;
20 
21 import com.android.internal.widget.remotecompose.core.CoreDocument;
22 import com.android.internal.widget.remotecompose.core.Operation;
23 import com.android.internal.widget.remotecompose.core.Operations;
24 import com.android.internal.widget.remotecompose.core.PaintContext;
25 import com.android.internal.widget.remotecompose.core.PaintOperation;
26 import com.android.internal.widget.remotecompose.core.RemoteContext;
27 import com.android.internal.widget.remotecompose.core.WireBuffer;
28 import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
29 import com.android.internal.widget.remotecompose.core.operations.TextData;
30 import com.android.internal.widget.remotecompose.core.operations.Utils;
31 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ModifierOperation;
32 import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
33 import com.android.internal.widget.remotecompose.core.operations.utilities.ColorUtils;
34 import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
35 import com.android.internal.widget.remotecompose.core.operations.utilities.easing.Easing;
36 import com.android.internal.widget.remotecompose.core.operations.utilities.easing.FloatAnimation;
37 import com.android.internal.widget.remotecompose.core.semantics.AccessibleComponent;
38 import com.android.internal.widget.remotecompose.core.semantics.CoreSemantics;
39 import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
40 import com.android.internal.widget.remotecompose.core.serialize.SerializeTags;
41 
42 import java.util.ArrayList;
43 import java.util.List;
44 
45 /** Represents a click modifier + actions */
46 public class ClickModifierOperation extends PaintOperation
47         implements Container,
48                 ModifierOperation,
49                 DecoratorComponent,
50                 ClickHandler,
51                 AccessibleComponent {
52     private static final int OP_CODE = Operations.MODIFIER_CLICK;
53 
54     long mAnimateRippleStart = 0;
55     float mAnimateRippleX = 0f;
56     float mAnimateRippleY = 0f;
57     int mAnimateRippleDuration = 1000;
58 
59     float mWidth = 0;
60     float mHeight = 0;
61 
62     @NonNull public float[] locationInWindow = new float[2];
63 
64     @NonNull PaintBundle mPaint = new PaintBundle();
65 
66     @Override
isClickable()67     public boolean isClickable() {
68         return true;
69     }
70 
71     @Nullable
72     @Override
getRole()73     public Role getRole() {
74         return Role.BUTTON;
75     }
76 
77     @Override
getMode()78     public CoreSemantics.Mode getMode() {
79         return CoreSemantics.Mode.MERGE;
80     }
81 
82     /**
83      * Animate ripple
84      *
85      * @param x starting position x of the ripple
86      * @param y starting position y of the ripple
87      */
animateRipple(float x, float y)88     public void animateRipple(float x, float y) {
89         mAnimateRippleStart = System.currentTimeMillis();
90         mAnimateRippleX = x;
91         mAnimateRippleY = y;
92     }
93 
94     @NonNull public ArrayList<Operation> mList = new ArrayList<>();
95 
96     @NonNull
getList()97     public ArrayList<Operation> getList() {
98         return mList;
99     }
100 
101     @Override
write(@onNull WireBuffer buffer)102     public void write(@NonNull WireBuffer buffer) {
103         apply(buffer);
104     }
105 
106     @NonNull
107     @Override
toString()108     public String toString() {
109         return "ClickModifier";
110     }
111 
112     @Override
apply(@onNull RemoteContext context)113     public void apply(@NonNull RemoteContext context) {
114         RootLayoutComponent root = context.getDocument().getRootLayoutComponent();
115         if (root != null) {
116             root.setHasTouchListeners(true);
117         }
118         for (Operation op : mList) {
119             if (op instanceof TextData) {
120                 op.apply(context);
121                 context.incrementOpCount();
122             }
123         }
124     }
125 
126     @NonNull
127     @Override
deepToString(@onNull String indent)128     public String deepToString(@NonNull String indent) {
129         return (indent != null ? indent : "") + toString();
130     }
131 
132     @Override
paint(@onNull PaintContext context)133     public void paint(@NonNull PaintContext context) {
134         if (mAnimateRippleStart == 0) {
135             return;
136         }
137         context.needsRepaint();
138 
139         float progress = (System.currentTimeMillis() - mAnimateRippleStart);
140         progress /= (float) mAnimateRippleDuration;
141         if (progress > 1f) {
142             mAnimateRippleStart = 0;
143         }
144         progress = Math.min(1f, progress);
145         context.save();
146         context.savePaint();
147         mPaint.reset();
148 
149         FloatAnimation anim1 =
150                 new FloatAnimation(Easing.CUBIC_STANDARD, 1f, null, Float.NaN, Float.NaN);
151         anim1.setInitialValue(0f);
152         anim1.setTargetValue(1f);
153         float tween = anim1.get(progress);
154 
155         FloatAnimation anim2 =
156                 new FloatAnimation(Easing.CUBIC_STANDARD, 0.5f, null, Float.NaN, Float.NaN);
157         anim2.setInitialValue(0f);
158         anim2.setTargetValue(1f);
159         float tweenRadius = anim2.get(progress);
160 
161         int startColor = ColorUtils.createColor(250, 250, 250, 180);
162         int endColor = ColorUtils.createColor(200, 200, 200, 0);
163         int paintedColor = Utils.interpolateColor(startColor, endColor, tween);
164 
165         float radius = Math.max(mWidth, mHeight) * tweenRadius;
166         mPaint.setColor(paintedColor);
167         context.applyPaint(mPaint);
168         context.clipRect(0f, 0f, mWidth, mHeight);
169         context.drawCircle(mAnimateRippleX, mAnimateRippleY, radius);
170         context.restorePaint();
171         context.restore();
172     }
173 
174     @Override
layout( @onNull RemoteContext context, Component component, float width, float height)175     public void layout(
176             @NonNull RemoteContext context, Component component, float width, float height) {
177         mWidth = width;
178         mHeight = height;
179     }
180 
181     @Override
serializeToString(int indent, @NonNull StringSerializer serializer)182     public void serializeToString(int indent, @NonNull StringSerializer serializer) {
183         serializer.append(indent, "CLICK_MODIFIER");
184         for (Operation o : mList) {
185             if (o instanceof ActionOperation) {
186                 ((ActionOperation) o).serializeToString(indent + 1, serializer);
187             }
188         }
189     }
190 
191     @Override
onClick( @onNull RemoteContext context, @NonNull CoreDocument document, @NonNull Component component, float x, float y)192     public void onClick(
193             @NonNull RemoteContext context,
194             @NonNull CoreDocument document,
195             @NonNull Component component,
196             float x,
197             float y) {
198         if (!component.isVisible()) {
199             return;
200         }
201         locationInWindow[0] = 0f;
202         locationInWindow[1] = 0f;
203         component.getLocationInWindow(locationInWindow);
204         animateRipple(x - locationInWindow[0], y - locationInWindow[1]);
205         for (Operation o : mList) {
206             if (o instanceof ActionOperation) {
207                 ((ActionOperation) o).runAction(context, document, component, x, y);
208             }
209         }
210         context.hapticEffect(3);
211     }
212 
213     /**
214      * The name of the class
215      *
216      * @return the name
217      */
218     @NonNull
name()219     public static String name() {
220         return "ClickModifier";
221     }
222 
223     /**
224      * Write the operation on the buffer
225      *
226      * @param buffer
227      */
apply(@onNull WireBuffer buffer)228     public static void apply(@NonNull WireBuffer buffer) {
229         buffer.start(OP_CODE);
230     }
231 
232     /**
233      * Read this operation and add it to the list of operations
234      *
235      * @param buffer the buffer to read
236      * @param operations the list of operations that will be added to
237      */
read(@onNull WireBuffer buffer, @NonNull List<Operation> operations)238     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
239         operations.add(new ClickModifierOperation());
240     }
241 
242     /**
243      * Populate the documentation with a description of this operation
244      *
245      * @param doc to append the description to.
246      */
documentation(@onNull DocumentationBuilder doc)247     public static void documentation(@NonNull DocumentationBuilder doc) {
248         doc.operation("Layout Operations", OP_CODE, name())
249                 .description(
250                         "Click modifier. This operation contains"
251                                 + " a list of action executed on click");
252     }
253 
254     @Override
serialize(MapSerializer serializer)255     public void serialize(MapSerializer serializer) {
256         serializer.addTags(SerializeTags.MODIFIER).addType("ClickModifierOperation");
257     }
258 }
259