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.Operation; 22 import com.android.internal.widget.remotecompose.core.OperationInterface; 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.TouchListener; 26 import com.android.internal.widget.remotecompose.core.VariableSupport; 27 import com.android.internal.widget.remotecompose.core.operations.BitmapData; 28 import com.android.internal.widget.remotecompose.core.operations.ComponentData; 29 import com.android.internal.widget.remotecompose.core.operations.MatrixRestore; 30 import com.android.internal.widget.remotecompose.core.operations.MatrixSave; 31 import com.android.internal.widget.remotecompose.core.operations.MatrixTranslate; 32 import com.android.internal.widget.remotecompose.core.operations.layout.animation.AnimationSpec; 33 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentModifiers; 34 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentVisibilityOperation; 35 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.DimensionModifierOperation; 36 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.GraphicsLayerModifierOperation; 37 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightInModifierOperation; 38 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightModifierOperation; 39 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ModifierOperation; 40 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.PaddingModifierOperation; 41 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ScrollModifierOperation; 42 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthInModifierOperation; 43 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthModifierOperation; 44 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ZIndexModifierOperation; 45 import com.android.internal.widget.remotecompose.core.serialize.MapSerializer; 46 import com.android.internal.widget.remotecompose.core.serialize.SerializeTags; 47 48 import java.util.ArrayList; 49 import java.util.HashMap; 50 51 /** Component with modifiers and children */ 52 public class LayoutComponent extends Component { 53 54 @Nullable protected WidthModifierOperation mWidthModifier = null; 55 @Nullable protected HeightModifierOperation mHeightModifier = null; 56 @Nullable protected ZIndexModifierOperation mZIndexModifier = null; 57 @Nullable protected GraphicsLayerModifierOperation mGraphicsLayerModifier = null; 58 59 protected float mPaddingLeft = 0f; 60 protected float mPaddingRight = 0f; 61 protected float mPaddingTop = 0f; 62 protected float mPaddingBottom = 0f; 63 64 float mScrollX = 0f; 65 float mScrollY = 0f; 66 67 @Nullable protected ScrollDelegate mHorizontalScrollDelegate = null; 68 @Nullable protected ScrollDelegate mVerticalScrollDelegate = null; 69 70 @NonNull protected ComponentModifiers mComponentModifiers = new ComponentModifiers(); 71 72 @NonNull 73 protected ArrayList<Component> mChildrenComponents = new ArrayList<>(); // members are not null 74 75 protected boolean mChildrenHaveZIndex = false; 76 private CanvasOperations mDrawContentOperations; 77 LayoutComponent( @ullable Component parent, int componentId, int animationId, float x, float y, float width, float height)78 public LayoutComponent( 79 @Nullable Component parent, 80 int componentId, 81 int animationId, 82 float x, 83 float y, 84 float width, 85 float height) { 86 super(parent, componentId, animationId, x, y, width, height); 87 } 88 getPaddingLeft()89 public float getPaddingLeft() { 90 return mPaddingLeft; 91 } 92 getPaddingTop()93 public float getPaddingTop() { 94 return mPaddingTop; 95 } 96 getPaddingRight()97 public float getPaddingRight() { 98 return mPaddingRight; 99 } 100 getPaddingBottom()101 public float getPaddingBottom() { 102 return mPaddingBottom; 103 } 104 105 @Nullable getWidthModifier()106 public WidthModifierOperation getWidthModifier() { 107 return mWidthModifier; 108 } 109 110 @Nullable getHeightModifier()111 public HeightModifierOperation getHeightModifier() { 112 return mHeightModifier; 113 } 114 115 @Override getZIndex()116 public float getZIndex() { 117 if (mZIndexModifier != null) { 118 return mZIndexModifier.getValue(); 119 } 120 return mZIndex; 121 } 122 123 @Nullable protected LayoutComponentContent mContent = null; 124 125 // Should be removed after ImageLayout is in 126 private static final boolean USE_IMAGE_TEMP_FIX = true; 127 128 /** 129 * Set canvas operations op on this component 130 * 131 * @param operations 132 */ setCanvasOperations(@ullable CanvasOperations operations)133 public void setCanvasOperations(@Nullable CanvasOperations operations) { 134 mDrawContentOperations = operations; 135 } 136 137 @Override inflate()138 public void inflate() { 139 ArrayList<Operation> data = new ArrayList<>(); 140 ArrayList<Operation> supportedOperations = new ArrayList<>(); 141 142 for (Operation op : mList) { 143 if (op instanceof LayoutComponentContent) { 144 mContent = (LayoutComponentContent) op; 145 mContent.mParent = this; 146 mChildrenComponents.clear(); 147 LayoutComponentContent content = (LayoutComponentContent) op; 148 content.getComponents(mChildrenComponents); 149 if (USE_IMAGE_TEMP_FIX) { 150 if (mChildrenComponents.isEmpty() && !mContent.mList.isEmpty()) { 151 CanvasContent canvasContent = 152 new CanvasContent(-1, 0f, 0f, 0f, 0f, this, -1); 153 for (Operation opc : mContent.mList) { 154 if (opc instanceof BitmapData) { 155 canvasContent.mList.add(opc); 156 int w = ((BitmapData) opc).getWidth(); 157 int h = ((BitmapData) opc).getHeight(); 158 canvasContent.setWidth(w); 159 canvasContent.setHeight(h); 160 } else { 161 if (!((opc instanceof MatrixTranslate) 162 || (opc instanceof MatrixSave) 163 || (opc instanceof MatrixRestore))) { 164 canvasContent.mList.add(opc); 165 } 166 } 167 } 168 if (!canvasContent.mList.isEmpty()) { 169 mContent.mList.clear(); 170 mChildrenComponents.add(canvasContent); 171 canvasContent.inflate(); 172 } 173 } else { 174 content.getData(data); 175 } 176 } else { 177 content.getData(data); 178 } 179 } else if (op instanceof ModifierOperation) { 180 if (op instanceof ComponentVisibilityOperation) { 181 ((ComponentVisibilityOperation) op).setParent(this); 182 } 183 if (op instanceof ScrollModifierOperation) { 184 ((ScrollModifierOperation) op).inflate(this); 185 } 186 mComponentModifiers.add((ModifierOperation) op); 187 } else if (op instanceof ComponentData) { 188 supportedOperations.add(op); 189 if (op instanceof TouchListener) { 190 ((TouchListener) op).setComponent(this); 191 } 192 } else { 193 // nothing 194 } 195 } 196 197 mList.clear(); 198 mList.addAll(data); 199 mList.addAll(supportedOperations); 200 mList.add(mComponentModifiers); 201 for (Component c : mChildrenComponents) { 202 c.mParent = this; 203 mList.add(c); 204 if (c instanceof LayoutComponent && ((LayoutComponent) c).mZIndexModifier != null) { 205 mChildrenHaveZIndex = true; 206 } 207 } 208 209 mX = 0f; 210 mY = 0f; 211 mPaddingLeft = 0f; 212 mPaddingTop = 0f; 213 mPaddingRight = 0f; 214 mPaddingBottom = 0f; 215 216 WidthInModifierOperation widthInConstraints = null; 217 HeightInModifierOperation heightInConstraints = null; 218 219 for (OperationInterface op : mComponentModifiers.getList()) { 220 if (op instanceof PaddingModifierOperation) { 221 // We are accumulating padding modifiers to compute the margin 222 // until we hit a dimension; the computed padding for the 223 // content simply accumulate all the padding modifiers. 224 float left = ((PaddingModifierOperation) op).getLeft(); 225 float right = ((PaddingModifierOperation) op).getRight(); 226 float top = ((PaddingModifierOperation) op).getTop(); 227 float bottom = ((PaddingModifierOperation) op).getBottom(); 228 mPaddingLeft += left; 229 mPaddingTop += top; 230 mPaddingRight += right; 231 mPaddingBottom += bottom; 232 } else if (op instanceof WidthModifierOperation && mWidthModifier == null) { 233 mWidthModifier = (WidthModifierOperation) op; 234 } else if (op instanceof HeightModifierOperation && mHeightModifier == null) { 235 mHeightModifier = (HeightModifierOperation) op; 236 } else if (op instanceof WidthInModifierOperation) { 237 widthInConstraints = (WidthInModifierOperation) op; 238 } else if (op instanceof HeightInModifierOperation) { 239 heightInConstraints = (HeightInModifierOperation) op; 240 } else if (op instanceof ZIndexModifierOperation) { 241 mZIndexModifier = (ZIndexModifierOperation) op; 242 } else if (op instanceof GraphicsLayerModifierOperation) { 243 mGraphicsLayerModifier = (GraphicsLayerModifierOperation) op; 244 } else if (op instanceof AnimationSpec) { 245 mAnimationSpec = (AnimationSpec) op; 246 } else if (op instanceof ScrollDelegate) { 247 ScrollDelegate scrollDelegate = (ScrollDelegate) op; 248 if (scrollDelegate.handlesHorizontalScroll()) { 249 mHorizontalScrollDelegate = scrollDelegate; 250 } 251 if (scrollDelegate.handlesVerticalScroll()) { 252 mVerticalScrollDelegate = scrollDelegate; 253 } 254 } 255 } 256 if (mWidthModifier == null) { 257 mWidthModifier = new WidthModifierOperation(DimensionModifierOperation.Type.WRAP); 258 } 259 if (mHeightModifier == null) { 260 mHeightModifier = new HeightModifierOperation(DimensionModifierOperation.Type.WRAP); 261 } 262 if (widthInConstraints != null) { 263 mWidthModifier.setWidthIn(widthInConstraints); 264 } 265 if (heightInConstraints != null) { 266 mHeightModifier.setHeightIn(heightInConstraints); 267 } 268 269 if (mAnimationSpec != AnimationSpec.DEFAULT) { 270 for (int i = 0; i < mChildrenComponents.size(); i++) { 271 Component c = mChildrenComponents.get(i); 272 if (c != null && c.getAnimationSpec() == AnimationSpec.DEFAULT) { 273 c.setAnimationSpec(mAnimationSpec); 274 } 275 } 276 } 277 278 setWidth(computeModifierDefinedWidth(null)); 279 setHeight(computeModifierDefinedHeight(null)); 280 } 281 282 @NonNull 283 @Override toString()284 public String toString() { 285 return "UNKNOWN LAYOUT_COMPONENT"; 286 } 287 288 @Override getLocationInWindow(@onNull float[] value, boolean forSelf)289 public void getLocationInWindow(@NonNull float[] value, boolean forSelf) { 290 value[0] += mX + mPaddingLeft; 291 value[1] += mY + mPaddingTop; 292 if (mParent != null) { 293 mParent.getLocationInWindow(value, false); 294 } 295 } 296 297 @Override getScrollX()298 public float getScrollX() { 299 if (mHorizontalScrollDelegate != null) { 300 return mHorizontalScrollDelegate.getScrollX(mScrollX); 301 } 302 return mScrollX; 303 } 304 setScrollX(float value)305 public void setScrollX(float value) { 306 mScrollX = value; 307 } 308 309 @Override getScrollY()310 public float getScrollY() { 311 if (mVerticalScrollDelegate != null) { 312 return mVerticalScrollDelegate.getScrollY(mScrollY); 313 } 314 return mScrollY; 315 } 316 setScrollY(float value)317 public void setScrollY(float value) { 318 mScrollY = value; 319 } 320 321 @Override paint(@onNull PaintContext context)322 public void paint(@NonNull PaintContext context) { 323 if (mDrawContentOperations != null) { 324 context.save(); 325 context.translate(mX, mY); 326 mDrawContentOperations.paint(context); 327 context.restore(); 328 return; 329 } 330 super.paint(context); 331 } 332 333 /** 334 * Paint the component content. Used by the DrawContent operation. (back out mX/mY -- TODO: 335 * refactor paintingComponent instead, to not include mX/mY etc.) 336 * 337 * @param context painting context 338 */ drawContent(@onNull PaintContext context)339 public void drawContent(@NonNull PaintContext context) { 340 context.save(); 341 context.translate(-mX, -mY); 342 paintingComponent(context); 343 context.restore(); 344 } 345 346 protected final HashMap<Integer, Object> mCachedAttributes = new HashMap<>(); 347 348 @Override paintingComponent(@onNull PaintContext context)349 public void paintingComponent(@NonNull PaintContext context) { 350 Component prev = context.getContext().mLastComponent; 351 RemoteContext remoteContext = context.getContext(); 352 353 remoteContext.mLastComponent = this; 354 context.save(); 355 context.translate(mX, mY); 356 if (context.isVisualDebug()) { 357 debugBox(this, context); 358 } 359 if (mGraphicsLayerModifier != null) { 360 context.startGraphicsLayer((int) getWidth(), (int) getHeight()); 361 mCachedAttributes.clear(); 362 mGraphicsLayerModifier.fillInAttributes(mCachedAttributes); 363 context.setGraphicsLayer(mCachedAttributes); 364 } 365 mComponentModifiers.paint(context); 366 float tx = mPaddingLeft + getScrollX(); 367 float ty = mPaddingTop + getScrollY(); 368 context.translate(tx, ty); 369 if (mChildrenHaveZIndex) { 370 // TODO -- should only sort when something has changed 371 ArrayList<Component> sorted = new ArrayList<Component>(mChildrenComponents); 372 sorted.sort((a, b) -> (int) (a.getZIndex() - b.getZIndex())); 373 for (Component child : sorted) { 374 if (child.isDirty() && child instanceof VariableSupport) { 375 child.updateVariables(context.getContext()); 376 child.markNotDirty(); 377 } 378 remoteContext.incrementOpCount(); 379 child.paint(context); 380 } 381 } else { 382 for (Component child : mChildrenComponents) { 383 if (child.isDirty() && child instanceof VariableSupport) { 384 child.updateVariables(context.getContext()); 385 child.markNotDirty(); 386 } 387 remoteContext.incrementOpCount(); 388 child.paint(context); 389 } 390 } 391 if (mGraphicsLayerModifier != null) { 392 context.endGraphicsLayer(); 393 } 394 context.translate(-tx, -ty); 395 context.restore(); 396 context.getContext().mLastComponent = prev; 397 } 398 399 /** Traverse the modifiers to compute indicated dimension */ computeModifierDefinedWidth(@ullable RemoteContext context)400 public float computeModifierDefinedWidth(@Nullable RemoteContext context) { 401 float s = 0f; 402 float e = 0f; 403 float w = 0f; 404 for (OperationInterface c : mComponentModifiers.getList()) { 405 if (context != null && c.isDirty() && c instanceof VariableSupport) { 406 ((VariableSupport) c).updateVariables(context); 407 c.markNotDirty(); 408 } 409 if (c instanceof WidthModifierOperation) { 410 WidthModifierOperation o = (WidthModifierOperation) c; 411 if (o.getType() == DimensionModifierOperation.Type.EXACT 412 || o.getType() == DimensionModifierOperation.Type.EXACT_DP) { 413 w = o.getValue(); 414 } 415 break; 416 } 417 if (c instanceof PaddingModifierOperation) { 418 PaddingModifierOperation pop = (PaddingModifierOperation) c; 419 s += pop.getLeft(); 420 e += pop.getRight(); 421 } 422 } 423 return s + w + e; 424 } 425 426 /** 427 * Traverse the modifiers to compute padding width 428 * 429 * @param padding output start and end padding values 430 * @return padding width 431 */ computeModifierDefinedPaddingWidth(@onNull float[] padding)432 public float computeModifierDefinedPaddingWidth(@NonNull float[] padding) { 433 float s = 0f; 434 float e = 0f; 435 for (OperationInterface c : mComponentModifiers.getList()) { 436 if (c instanceof PaddingModifierOperation) { 437 PaddingModifierOperation pop = (PaddingModifierOperation) c; 438 s += pop.getLeft(); 439 e += pop.getRight(); 440 } 441 } 442 padding[0] = s; 443 padding[1] = e; 444 return s + e; 445 } 446 447 /** Traverse the modifiers to compute indicated dimension */ computeModifierDefinedHeight(@ullable RemoteContext context)448 public float computeModifierDefinedHeight(@Nullable RemoteContext context) { 449 float t = 0f; 450 float b = 0f; 451 float h = 0f; 452 for (OperationInterface c : mComponentModifiers.getList()) { 453 if (context != null && c.isDirty() && c instanceof VariableSupport) { 454 ((VariableSupport) c).updateVariables(context); 455 c.markNotDirty(); 456 } 457 if (c instanceof HeightModifierOperation) { 458 HeightModifierOperation o = (HeightModifierOperation) c; 459 if (o.getType() == DimensionModifierOperation.Type.EXACT 460 || o.getType() == DimensionModifierOperation.Type.EXACT_DP) { 461 h = o.getValue(); 462 } 463 break; 464 } 465 if (c instanceof PaddingModifierOperation) { 466 PaddingModifierOperation pop = (PaddingModifierOperation) c; 467 t += pop.getTop(); 468 b += pop.getBottom(); 469 } 470 } 471 return t + h + b; 472 } 473 474 /** 475 * Traverse the modifiers to compute padding height 476 * 477 * @param padding output top and bottom padding values 478 * @return padding height 479 */ computeModifierDefinedPaddingHeight(@onNull float[] padding)480 public float computeModifierDefinedPaddingHeight(@NonNull float[] padding) { 481 float t = 0f; 482 float b = 0f; 483 for (OperationInterface c : mComponentModifiers.getList()) { 484 if (c instanceof PaddingModifierOperation) { 485 PaddingModifierOperation pop = (PaddingModifierOperation) c; 486 t += pop.getTop(); 487 b += pop.getBottom(); 488 } 489 } 490 padding[0] = t; 491 padding[1] = b; 492 return t + b; 493 } 494 495 @NonNull getComponentModifiers()496 public ComponentModifiers getComponentModifiers() { 497 return mComponentModifiers; 498 } 499 500 @NonNull getChildrenComponents()501 public ArrayList<Component> getChildrenComponents() { 502 return mChildrenComponents; 503 } 504 505 @Override serialize(MapSerializer serializer)506 public void serialize(MapSerializer serializer) { 507 super.serialize(serializer); 508 serializer 509 .addTags(SerializeTags.LAYOUT_COMPONENT) 510 .add("paddingLeft", mPaddingLeft) 511 .add("paddingRight", mPaddingRight) 512 .add("paddingTop", mPaddingTop) 513 .add("paddingBottom", mPaddingBottom); 514 } 515 516 @Override selfOrModifier(Class<T> operationClass)517 public <T> @Nullable T selfOrModifier(Class<T> operationClass) { 518 if (operationClass.isInstance(this)) { 519 return operationClass.cast(this); 520 } 521 522 for (ModifierOperation op : mComponentModifiers.getList()) { 523 if (operationClass.isInstance(op)) { 524 return operationClass.cast(op); 525 } 526 } 527 528 return null; 529 } 530 531 @Override registerVariables(RemoteContext context)532 public void registerVariables(RemoteContext context) { 533 if (mDrawContentOperations != null) { 534 mDrawContentOperations.registerListening(context); 535 } 536 } 537 } 538