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 static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; 19 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 23 import com.android.internal.widget.remotecompose.core.Operation; 24 import com.android.internal.widget.remotecompose.core.Operations; 25 import com.android.internal.widget.remotecompose.core.PaintContext; 26 import com.android.internal.widget.remotecompose.core.PaintOperation; 27 import com.android.internal.widget.remotecompose.core.RemoteContext; 28 import com.android.internal.widget.remotecompose.core.SerializableToString; 29 import com.android.internal.widget.remotecompose.core.WireBuffer; 30 import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; 31 import com.android.internal.widget.remotecompose.core.operations.layout.measure.Measurable; 32 import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass; 33 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentModifiers; 34 import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer; 35 import com.android.internal.widget.remotecompose.core.serialize.MapSerializer; 36 import com.android.internal.widget.remotecompose.core.serialize.SerializeTags; 37 38 import java.util.List; 39 40 /** Represents the root layout component. Entry point to the component tree layout/paint. */ 41 public class RootLayoutComponent extends Component { 42 private int mCurrentId = -1; 43 private boolean mHasTouchListeners = false; 44 RootLayoutComponent( int componentId, float x, float y, float width, float height, @Nullable Component parent, int animationId)45 public RootLayoutComponent( 46 int componentId, 47 float x, 48 float y, 49 float width, 50 float height, 51 @Nullable Component parent, 52 int animationId) { 53 super(parent, componentId, animationId, x, y, width, height); 54 } 55 RootLayoutComponent( int componentId, float x, float y, float width, float height, @Nullable Component parent)56 public RootLayoutComponent( 57 int componentId, 58 float x, 59 float y, 60 float width, 61 float height, 62 @Nullable Component parent) { 63 super(parent, componentId, -1, x, y, width, height); 64 } 65 66 @NonNull 67 @Override toString()68 public String toString() { 69 return "ROOT " 70 + mComponentId 71 + " (" 72 + mX 73 + ", " 74 + mY 75 + " - " 76 + mWidth 77 + " x " 78 + mHeight 79 + ") " 80 + Visibility.toString(mVisibility); 81 } 82 83 @Override serializeToString(int indent, @NonNull StringSerializer serializer)84 public void serializeToString(int indent, @NonNull StringSerializer serializer) { 85 serializer.append( 86 indent, 87 "ROOT [" 88 + mComponentId 89 + ":" 90 + mAnimationId 91 + "] = [" 92 + mX 93 + ", " 94 + mY 95 + ", " 96 + mWidth 97 + ", " 98 + mHeight 99 + "] " 100 + Visibility.toString(mVisibility)); 101 } 102 103 /** 104 * Set the flag to traverse the tree when touch events happen 105 * 106 * @param value true to indicate that the tree has touch listeners 107 */ setHasTouchListeners(boolean value)108 public void setHasTouchListeners(boolean value) { 109 mHasTouchListeners = value; 110 } 111 112 /** 113 * Traverse the hierarchy and assign generated ids to component without ids. Most components 114 * would already have ids assigned during the document creation, but this allow us to take care 115 * of any components added during the inflation. 116 * 117 * @param lastId the last known generated id 118 */ assignIds(int lastId)119 public void assignIds(int lastId) { 120 mCurrentId = lastId; 121 assignId(this); 122 } 123 assignId(@onNull Component component)124 private void assignId(@NonNull Component component) { 125 if (component.mComponentId == -1) { 126 mCurrentId--; 127 component.mComponentId = mCurrentId; 128 } 129 for (Operation op : component.mList) { 130 if (op instanceof Component) { 131 assignId((Component) op); 132 } 133 } 134 } 135 136 /** This will measure then layout the tree of components */ layout(@onNull RemoteContext context)137 public void layout(@NonNull RemoteContext context) { 138 if (!mNeedsMeasure) { 139 return; 140 } 141 context.mLastComponent = this; 142 setWidth(context.mWidth); 143 setHeight(context.mHeight); 144 145 // TODO: reuse MeasurePass 146 MeasurePass measurePass = new MeasurePass(); 147 for (Operation op : mList) { 148 if (op instanceof Measurable) { 149 Measurable m = (Measurable) op; 150 m.measure(context.getPaintContext(), 0f, mWidth, 0f, mHeight, measurePass); 151 m.layout(context, measurePass); 152 } 153 } 154 mNeedsMeasure = false; 155 } 156 157 @Override paint(@onNull PaintContext context)158 public void paint(@NonNull PaintContext context) { 159 mNeedsRepaint = false; 160 RemoteContext remoteContext = context.getContext(); 161 remoteContext.mLastComponent = this; 162 163 context.save(); 164 165 if (mParent == null) { // root layout 166 context.clipRect(0f, 0f, mWidth, mHeight); 167 } 168 169 for (Operation op : mList) { 170 if (op instanceof PaintOperation) { 171 ((PaintOperation) op).paint(context); 172 remoteContext.incrementOpCount(); 173 } 174 } 175 176 context.restore(); 177 } 178 179 /** 180 * Display the component hierarchy 181 * 182 * @return a formatted string containing the component hierarchy 183 */ 184 @NonNull displayHierarchy()185 public String displayHierarchy() { 186 StringSerializer serializer = new StringSerializer(); 187 displayHierarchy(this, 0, serializer); 188 return serializer.toString(); 189 } 190 191 /** 192 * Display the component hierarchy 193 * 194 * @param component the current component 195 * @param indent the current indentation level 196 * @param serializer the serializer we write to 197 */ displayHierarchy( @onNull Component component, int indent, @NonNull StringSerializer serializer)198 public void displayHierarchy( 199 @NonNull Component component, int indent, @NonNull StringSerializer serializer) { 200 component.serializeToString(indent, serializer); 201 for (Operation c : component.mList) { 202 if (c instanceof ComponentModifiers) { 203 ((ComponentModifiers) c).serializeToString(indent + 1, serializer); 204 } else if (c instanceof Component) { 205 displayHierarchy((Component) c, indent + 1, serializer); 206 } else if (c instanceof SerializableToString) { 207 ((SerializableToString) c).serializeToString(indent + 1, serializer); 208 } 209 } 210 } 211 212 /** 213 * The name of the class 214 * 215 * @return the name 216 */ 217 @NonNull name()218 public static String name() { 219 return "RootLayout"; 220 } 221 222 /** 223 * The OP_CODE for this command 224 * 225 * @return the opcode 226 */ id()227 public static int id() { 228 return Operations.LAYOUT_ROOT; 229 } 230 231 /** 232 * Write the operation on the buffer 233 * 234 * @param buffer 235 * @param componentId 236 */ apply(@onNull WireBuffer buffer, int componentId)237 public static void apply(@NonNull WireBuffer buffer, int componentId) { 238 buffer.start(Operations.LAYOUT_ROOT); 239 buffer.writeInt(componentId); 240 } 241 242 /** 243 * Read this operation and add it to the list of operations 244 * 245 * @param buffer the buffer to read 246 * @param operations the list of operations that will be added to 247 */ read(@onNull WireBuffer buffer, @NonNull List<Operation> operations)248 public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { 249 int componentId = buffer.readInt(); 250 operations.add(new RootLayoutComponent(componentId, 0, 0, 0, 0, null, -1)); 251 } 252 253 /** 254 * Populate the documentation with a description of this operation 255 * 256 * @param doc to append the description to. 257 */ documentation(@onNull DocumentationBuilder doc)258 public static void documentation(@NonNull DocumentationBuilder doc) { 259 doc.operation("Layout Operations", id(), name()) 260 .field(INT, "COMPONENT_ID", "unique id for this component") 261 .description( 262 "Root element for a document. Other components / layout managers are" 263 + " children in the component tree starting from" 264 + "this Root component."); 265 } 266 267 @Override write(@onNull WireBuffer buffer)268 public void write(@NonNull WireBuffer buffer) { 269 apply(buffer, mComponentId); 270 } 271 272 /** 273 * Returns true if we have components with a touch listener 274 * 275 * @return true if listeners, false otherwise 276 */ hasTouchListeners()277 public boolean hasTouchListeners() { 278 return mHasTouchListeners; 279 } 280 281 @Override serialize(MapSerializer serializer)282 public void serialize(MapSerializer serializer) { 283 super.serialize(serializer); 284 serializer.addTags(SerializeTags.COMPONENT); 285 serializer.addType("RootLayoutComponent"); 286 } 287 } 288