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