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.PaintContext; 24 import com.android.internal.widget.remotecompose.core.PaintOperation; 25 import com.android.internal.widget.remotecompose.core.RemoteContext; 26 import com.android.internal.widget.remotecompose.core.SerializableToString; 27 import com.android.internal.widget.remotecompose.core.TouchListener; 28 import com.android.internal.widget.remotecompose.core.VariableSupport; 29 import com.android.internal.widget.remotecompose.core.WireBuffer; 30 import com.android.internal.widget.remotecompose.core.operations.BitmapData; 31 import com.android.internal.widget.remotecompose.core.operations.ComponentValue; 32 import com.android.internal.widget.remotecompose.core.operations.TextData; 33 import com.android.internal.widget.remotecompose.core.operations.TouchExpression; 34 import com.android.internal.widget.remotecompose.core.operations.layout.animation.AnimateMeasure; 35 import com.android.internal.widget.remotecompose.core.operations.layout.animation.AnimationSpec; 36 import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure; 37 import com.android.internal.widget.remotecompose.core.operations.layout.measure.Measurable; 38 import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass; 39 import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle; 40 import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer; 41 import com.android.internal.widget.remotecompose.core.serialize.MapSerializer; 42 import com.android.internal.widget.remotecompose.core.serialize.Serializable; 43 import com.android.internal.widget.remotecompose.core.serialize.SerializeTags; 44 45 import java.util.ArrayList; 46 import java.util.HashSet; 47 48 /** Generic Component class */ 49 public class Component extends PaintOperation 50 implements Container, Measurable, SerializableToString, Serializable { 51 52 private static final boolean DEBUG = false; 53 54 protected int mComponentId = -1; 55 protected float mX; 56 protected float mY; 57 protected float mWidth; 58 protected float mHeight; 59 @Nullable protected Component mParent; 60 protected int mAnimationId = -1; 61 public int mVisibility = Visibility.VISIBLE; 62 public int mScheduledVisibility = Visibility.VISIBLE; 63 @NonNull public ArrayList<Operation> mList = new ArrayList<>(); 64 public PaintOperation mPreTranslate; // todo, can we initialize this here and make it NonNull? 65 public boolean mNeedsMeasure = true; 66 public boolean mNeedsRepaint = false; 67 @Nullable public AnimateMeasure mAnimateMeasure; 68 @NonNull public AnimationSpec mAnimationSpec = AnimationSpec.DEFAULT; 69 public boolean mFirstLayout = true; 70 @NonNull PaintBundle mPaint = new PaintBundle(); 71 @NonNull protected HashSet<ComponentValue> mComponentValues = new HashSet<>(); 72 73 protected float mZIndex = 0f; 74 75 private boolean mNeedsBoundsAnimation = false; 76 77 /** Mark the component as needing a bounds animation pass */ markNeedsBoundsAnimation()78 public void markNeedsBoundsAnimation() { 79 mNeedsBoundsAnimation = true; 80 if (mParent != null && !mParent.mNeedsBoundsAnimation) { 81 mParent.markNeedsBoundsAnimation(); 82 } 83 } 84 85 /** Clear the bounds animation pass flag */ clearNeedsBoundsAnimation()86 public void clearNeedsBoundsAnimation() { 87 mNeedsBoundsAnimation = false; 88 } 89 90 /** 91 * True if needs a bounds animation 92 * 93 * @return true if needs a bounds animation pass 94 */ needsBoundsAnimation()95 public boolean needsBoundsAnimation() { 96 return mNeedsBoundsAnimation; 97 } 98 getZIndex()99 public float getZIndex() { 100 return mZIndex; 101 } 102 103 @NonNull getList()104 public ArrayList<Operation> getList() { 105 return mList; 106 } 107 getX()108 public float getX() { 109 return mX; 110 } 111 getY()112 public float getY() { 113 return mY; 114 } 115 getWidth()116 public float getWidth() { 117 return mWidth; 118 } 119 getHeight()120 public float getHeight() { 121 return mHeight; 122 } 123 getComponentId()124 public int getComponentId() { 125 return mComponentId; 126 } 127 getAnimationId()128 public int getAnimationId() { 129 return mAnimationId; 130 } 131 132 @Nullable getParent()133 public Component getParent() { 134 return mParent; 135 } 136 setX(float value)137 public void setX(float value) { 138 mX = value; 139 } 140 setY(float value)141 public void setY(float value) { 142 mY = value; 143 } 144 setWidth(float value)145 public void setWidth(float value) { 146 mWidth = value; 147 } 148 setHeight(float value)149 public void setHeight(float value) { 150 mHeight = value; 151 } 152 153 @Override apply(@onNull RemoteContext context)154 public void apply(@NonNull RemoteContext context) { 155 for (Operation op : mList) { 156 if (op instanceof VariableSupport && op.isDirty()) { 157 op.markNotDirty(); 158 ((VariableSupport) op).updateVariables(context); 159 } 160 } 161 super.apply(context); 162 } 163 164 /** 165 * Utility function to update variables referencing this component dimensions 166 * 167 * @param context the current context 168 */ updateComponentValues(@onNull RemoteContext context)169 private void updateComponentValues(@NonNull RemoteContext context) { 170 if (DEBUG) { 171 System.out.println( 172 "UPDATE COMPONENT VALUES (" 173 + mComponentValues.size() 174 + ") FOR " 175 + mComponentId); 176 } 177 for (ComponentValue v : mComponentValues) { 178 if (context.getMode() == RemoteContext.ContextMode.DATA) { 179 context.loadFloat(v.getValueId(), 1f); 180 } else { 181 switch (v.getType()) { 182 case ComponentValue.WIDTH: 183 context.loadFloat(v.getValueId(), mWidth); 184 if (DEBUG) { 185 System.out.println( 186 "Updating WIDTH for " + mComponentId + " to " + mWidth); 187 } 188 break; 189 case ComponentValue.HEIGHT: 190 context.loadFloat(v.getValueId(), mHeight); 191 if (DEBUG) { 192 System.out.println( 193 "Updating HEIGHT for " + mComponentId + " to " + mHeight); 194 } 195 break; 196 } 197 } 198 } 199 } 200 setComponentId(int id)201 public void setComponentId(int id) { 202 mComponentId = id; 203 } 204 setAnimationId(int id)205 public void setAnimationId(int id) { 206 mAnimationId = id; 207 } 208 Component( @ullable Component parent, int componentId, int animationId, float x, float y, float width, float height)209 public Component( 210 @Nullable Component parent, 211 int componentId, 212 int animationId, 213 float x, 214 float y, 215 float width, 216 float height) { 217 this.mComponentId = componentId; 218 this.mX = x; 219 this.mY = y; 220 this.mWidth = width; 221 this.mHeight = height; 222 this.mParent = parent; 223 this.mAnimationId = animationId; 224 } 225 Component( int componentId, float x, float y, float width, float height, @Nullable Component parent)226 public Component( 227 int componentId, 228 float x, 229 float y, 230 float width, 231 float height, 232 @Nullable Component parent) { 233 this(parent, componentId, -1, x, y, width, height); 234 } 235 Component(@onNull Component component)236 public Component(@NonNull Component component) { 237 this( 238 component.mParent, 239 component.mComponentId, 240 component.mAnimationId, 241 component.mX, 242 component.mY, 243 component.mWidth, 244 component.mHeight); 245 mList.addAll(component.mList); 246 finalizeCreation(); 247 } 248 249 /** Callback on component creation TODO: replace with inflate() */ finalizeCreation()250 public void finalizeCreation() { 251 for (Operation op : mList) { 252 if (op instanceof Component) { 253 ((Component) op).mParent = this; 254 } 255 if (op instanceof AnimationSpec) { 256 mAnimationSpec = (AnimationSpec) op; 257 mAnimationId = mAnimationSpec.getAnimationId(); 258 } 259 } 260 } 261 262 @Override needsMeasure()263 public boolean needsMeasure() { 264 return mNeedsMeasure; 265 } 266 setParent(@ullable Component parent)267 public void setParent(@Nullable Component parent) { 268 mParent = parent; 269 } 270 271 /** 272 * This traverses the component tree and make sure to update variables referencing the component 273 * dimensions as needed. 274 * 275 * @param context the current context 276 */ updateVariables(@onNull RemoteContext context)277 public void updateVariables(@NonNull RemoteContext context) { 278 Component prev = context.mLastComponent; 279 context.mLastComponent = this; 280 281 if (!mComponentValues.isEmpty()) { 282 updateComponentValues(context); 283 } 284 context.mLastComponent = prev; 285 } 286 287 /** 288 * Add a component value to the component 289 * 290 * @param v 291 */ addComponentValue(@onNull ComponentValue v)292 public void addComponentValue(@NonNull ComponentValue v) { 293 mComponentValues.add(v); 294 } 295 296 /** 297 * Returns the min intrinsic width of the layout 298 * 299 * @param context 300 * @return the width in pixels 301 */ minIntrinsicWidth(@ullable RemoteContext context)302 public float minIntrinsicWidth(@Nullable RemoteContext context) { 303 return getWidth(); 304 } 305 306 /** 307 * Returns the max intrinsic width of the layout 308 * 309 * @param context 310 * @return the width in pixels 311 */ maxIntrinsicWidth(@ullable RemoteContext context)312 public float maxIntrinsicWidth(@Nullable RemoteContext context) { 313 return getWidth(); 314 } 315 316 /** 317 * Returns the min intrinsic height of the layout 318 * 319 * @param context 320 * @return the height in pixels 321 */ minIntrinsicHeight(@ullable RemoteContext context)322 public float minIntrinsicHeight(@Nullable RemoteContext context) { 323 return getHeight(); 324 } 325 326 /** 327 * Returns the max intrinsic height of the layout 328 * 329 * @param context 330 * @return the height in pixels 331 */ maxIntrinsicHeight(@ullable RemoteContext context)332 public float maxIntrinsicHeight(@Nullable RemoteContext context) { 333 return getHeight(); 334 } 335 336 /** 337 * This function is called after a component is created, with its mList initialized. This let 338 * the component a chance to do some post-initialization work on its children ops. 339 */ inflate()340 public void inflate() { 341 for (Operation op : mList) { 342 if (op instanceof TouchListener) { 343 // Make sure to set the component of a touch expression that belongs to us! 344 TouchListener touchListener = (TouchListener) op; 345 touchListener.setComponent(this); 346 } 347 } 348 } 349 getAnimationSpec()350 protected AnimationSpec getAnimationSpec() { 351 return mAnimationSpec; 352 } 353 setAnimationSpec(@onNull AnimationSpec animationSpec)354 protected void setAnimationSpec(@NonNull AnimationSpec animationSpec) { 355 mAnimationSpec = animationSpec; 356 } 357 358 /** 359 * If the component contains variables beside mList, make sure to register them here 360 * 361 * @param context 362 */ registerVariables(RemoteContext context)363 public void registerVariables(RemoteContext context) { 364 // Nothing here 365 } 366 367 public static class Visibility { 368 369 public static final int GONE = 0; 370 public static final int VISIBLE = 1; 371 public static final int INVISIBLE = 2; 372 public static final int OVERRIDE_GONE = 16; 373 public static final int OVERRIDE_VISIBLE = 32; 374 public static final int OVERRIDE_INVISIBLE = 64; 375 public static final int CLEAR_OVERRIDE = 128; 376 377 /** 378 * Returns a string representation of the field 379 * 380 * @param value 381 * @return 382 */ toString(int value)383 public static String toString(int value) { 384 switch (value) { 385 case GONE: 386 return "GONE"; 387 case VISIBLE: 388 return "VISIBLE"; 389 case INVISIBLE: 390 return "INVISIBLE"; 391 } 392 if ((value >> 4) > 0) { 393 if ((value & OVERRIDE_GONE) == OVERRIDE_GONE) { 394 return "OVERRIDE_GONE"; 395 } 396 if ((value & OVERRIDE_VISIBLE) == OVERRIDE_VISIBLE) { 397 return "OVERRIDE_VISIBLE"; 398 } 399 if ((value & OVERRIDE_INVISIBLE) == OVERRIDE_INVISIBLE) { 400 return "OVERRIDE_INVISIBLE"; 401 } 402 } 403 return "" + value; 404 } 405 406 /** 407 * Returns true if gone 408 * 409 * @param value 410 * @return 411 */ isGone(int value)412 public static boolean isGone(int value) { 413 if ((value >> 4) > 0) { 414 return (value & OVERRIDE_GONE) == OVERRIDE_GONE; 415 } 416 return value == GONE; 417 } 418 419 /** 420 * Returns true if visible 421 * 422 * @param value 423 * @return 424 */ isVisible(int value)425 public static boolean isVisible(int value) { 426 if ((value >> 4) > 0) { 427 return (value & OVERRIDE_VISIBLE) == OVERRIDE_VISIBLE; 428 } 429 return value == VISIBLE; 430 } 431 432 /** 433 * Returns true if invisible 434 * 435 * @param value 436 * @return 437 */ isInvisible(int value)438 public static boolean isInvisible(int value) { 439 if ((value >> 4) > 0) { 440 return (value & OVERRIDE_INVISIBLE) == OVERRIDE_INVISIBLE; 441 } 442 return value == INVISIBLE; 443 } 444 445 /** 446 * Returns true if the field has an override 447 * 448 * @param value 449 * @return 450 */ hasOverride(int value)451 public static boolean hasOverride(int value) { 452 return (value >> 4) > 0; 453 } 454 455 /** 456 * Clear the override values 457 * 458 * @param value 459 * @return 460 */ clearOverride(int value)461 public static int clearOverride(int value) { 462 return value & 15; 463 } 464 465 /** 466 * Add an override value 467 * 468 * @param value 469 * @param visibility 470 * @return 471 */ add(int value, int visibility)472 public static int add(int value, int visibility) { 473 int v = value & 15; 474 v += visibility; 475 if ((v & CLEAR_OVERRIDE) == CLEAR_OVERRIDE) { 476 v = v & 15; 477 } 478 return v; 479 } 480 } 481 482 /** 483 * Returns true if the component is visible 484 * 485 * @return 486 */ isVisible()487 public boolean isVisible() { 488 if (mParent == null || !Visibility.isVisible(mVisibility)) { 489 return Visibility.isVisible(mVisibility); 490 } 491 return mParent.isVisible(); 492 } 493 494 /** 495 * Returns true if the component is gone 496 * 497 * @return 498 */ isGone()499 public boolean isGone() { 500 return Visibility.isGone(mVisibility); 501 } 502 503 /** 504 * Returns true if the component is invisible 505 * 506 * @return 507 */ isInvisible()508 public boolean isInvisible() { 509 return Visibility.isInvisible(mVisibility); 510 } 511 512 /** 513 * Set the visibility of the component 514 * 515 * @param visibility can be VISIBLE, INVISIBLE or GONE 516 */ setVisibility(int visibility)517 public void setVisibility(int visibility) { 518 if (visibility != mVisibility || visibility != mScheduledVisibility) { 519 mScheduledVisibility = visibility; 520 invalidateMeasure(); 521 } 522 } 523 524 @Override suitableForTransition(@onNull Operation o)525 public boolean suitableForTransition(@NonNull Operation o) { 526 if (!(o instanceof Component)) { 527 return false; 528 } 529 if (mList.size() != ((Component) o).mList.size()) { 530 return false; 531 } 532 for (int i = 0; i < mList.size(); i++) { 533 Operation o1 = mList.get(i); 534 Operation o2 = ((Component) o).mList.get(i); 535 if (o1 instanceof Component && o2 instanceof Component) { 536 if (!((Component) o1).suitableForTransition(o2)) { 537 return false; 538 } 539 } 540 if (o1 instanceof PaintOperation && !((PaintOperation) o1).suitableForTransition(o2)) { 541 return false; 542 } 543 } 544 return true; 545 } 546 547 @Override measure( @onNull PaintContext context, float minWidth, float maxWidth, float minHeight, float maxHeight, @NonNull MeasurePass measure)548 public void measure( 549 @NonNull PaintContext context, 550 float minWidth, 551 float maxWidth, 552 float minHeight, 553 float maxHeight, 554 @NonNull MeasurePass measure) { 555 ComponentMeasure m = measure.get(this); 556 m.setW(mWidth); 557 m.setH(mHeight); 558 } 559 560 @Override layout(@onNull RemoteContext context, @NonNull MeasurePass measure)561 public void layout(@NonNull RemoteContext context, @NonNull MeasurePass measure) { 562 ComponentMeasure m = measure.get(this); 563 if (!mFirstLayout 564 && context.isAnimationEnabled() 565 && mAnimationSpec.isAnimationEnabled() 566 && !(this instanceof LayoutComponentContent)) { 567 if (mAnimateMeasure == null) { 568 ComponentMeasure origin = 569 new ComponentMeasure(mComponentId, mX, mY, mWidth, mHeight, mVisibility); 570 ComponentMeasure target = 571 new ComponentMeasure( 572 mComponentId, 573 m.getX(), 574 m.getY(), 575 m.getW(), 576 m.getH(), 577 m.getVisibility()); 578 if (!target.same(origin)) { 579 mAnimateMeasure = 580 new AnimateMeasure( 581 context.currentTime, 582 this, 583 origin, 584 target, 585 mAnimationSpec.getMotionDuration(), 586 mAnimationSpec.getVisibilityDuration(), 587 mAnimationSpec.getEnterAnimation(), 588 mAnimationSpec.getExitAnimation(), 589 mAnimationSpec.getMotionEasingType(), 590 mAnimationSpec.getVisibilityEasingType()); 591 } 592 } else { 593 mAnimateMeasure.updateTarget(m, context.currentTime); 594 } 595 } else { 596 mVisibility = m.getVisibility(); 597 } 598 if (mAnimateMeasure == null) { 599 setWidth(m.getW()); 600 setHeight(m.getH()); 601 setLayoutPosition(m.getX(), m.getY()); 602 updateComponentValues(context); 603 clearNeedsBoundsAnimation(); 604 } else { 605 mAnimateMeasure.apply(context); 606 updateComponentValues(context); 607 markNeedsBoundsAnimation(); 608 } 609 mFirstLayout = false; 610 } 611 612 /** 613 * Animate the bounds of the component as needed 614 * 615 * @param context 616 */ animatingBounds(@onNull RemoteContext context)617 public void animatingBounds(@NonNull RemoteContext context) { 618 if (mAnimateMeasure != null) { 619 mAnimateMeasure.apply(context); 620 updateComponentValues(context); 621 } else { 622 clearNeedsBoundsAnimation(); 623 } 624 for (Operation op : mList) { 625 if (op instanceof Measurable) { 626 Measurable m = (Measurable) op; 627 m.animatingBounds(context); 628 } 629 } 630 } 631 632 @NonNull public float[] locationInWindow = new float[2]; 633 634 /** 635 * Hit detection -- returns true if the point (x, y) is inside the component 636 * 637 * @param x 638 * @param y 639 * @return 640 */ contains(float x, float y)641 public boolean contains(float x, float y) { 642 locationInWindow[0] = 0f; 643 locationInWindow[1] = 0f; 644 getLocationInWindow(locationInWindow); 645 float lx1 = locationInWindow[0]; 646 float lx2 = lx1 + mWidth; 647 float ly1 = locationInWindow[1]; 648 float ly2 = ly1 + mHeight; 649 return x >= lx1 && x < lx2 && y >= ly1 && y < ly2; 650 } 651 652 /** 653 * Returns the horizontal scroll value of the content of this component 654 * 655 * @return 0 if no scroll 656 */ getScrollX()657 public float getScrollX() { 658 return 0; 659 } 660 661 /** 662 * Returns the vertical scroll value of the content of this component 663 * 664 * @return 0 if no scroll 665 */ getScrollY()666 public float getScrollY() { 667 return 0; 668 } 669 670 /** 671 * Click handler 672 * 673 * @param context 674 * @param document 675 * @param x x location on screen or -1 if unconditional click 676 * @param y y location on screen or -1 if unconditional click 677 */ onClick( @onNull RemoteContext context, @NonNull CoreDocument document, float x, float y)678 public void onClick( 679 @NonNull RemoteContext context, @NonNull CoreDocument document, float x, float y) { 680 boolean isUnconditional = x == -1 & y == -1; 681 if (!isUnconditional && !contains(x, y)) { 682 return; 683 } 684 float cx = isUnconditional ? -1 : x - getScrollX(); 685 float cy = isUnconditional ? -1 : y - getScrollY(); 686 for (Operation op : mList) { 687 if (op instanceof Component) { 688 ((Component) op).onClick(context, document, cx, cy); 689 } 690 if (op instanceof ClickHandler) { 691 ((ClickHandler) op).onClick(context, document, this, cx, cy); 692 } 693 } 694 } 695 696 /** 697 * Touch down handler 698 * 699 * @param context 700 * @param document 701 * @param x 702 * @param y 703 */ onTouchDown(RemoteContext context, CoreDocument document, float x, float y)704 public void onTouchDown(RemoteContext context, CoreDocument document, float x, float y) { 705 if (!contains(x, y)) { 706 return; 707 } 708 float cx = x - getScrollX(); 709 float cy = y - getScrollY(); 710 for (Operation op : mList) { 711 if (op instanceof Component) { 712 ((Component) op).onTouchDown(context, document, cx, cy); 713 } 714 if (op instanceof TouchHandler) { 715 ((TouchHandler) op).onTouchDown(context, document, this, cx, cy); 716 } 717 if (op instanceof TouchExpression) { 718 TouchExpression touchExpression = (TouchExpression) op; 719 touchExpression.updateVariables(context); 720 touchExpression.touchDown(context, cx, cy); 721 document.appliedTouchOperation(this); 722 } 723 } 724 } 725 726 /** 727 * Touch Up handler 728 * 729 * @param context 730 * @param document 731 * @param x 732 * @param y 733 * @param dx 734 * @param dy 735 * @param force 736 */ onTouchUp( RemoteContext context, CoreDocument document, float x, float y, float dx, float dy, boolean force)737 public void onTouchUp( 738 RemoteContext context, 739 CoreDocument document, 740 float x, 741 float y, 742 float dx, 743 float dy, 744 boolean force) { 745 if (!force && !contains(x, y)) { 746 return; 747 } 748 float cx = x - getScrollX(); 749 float cy = y - getScrollY(); 750 for (Operation op : mList) { 751 if (op instanceof Component) { 752 ((Component) op).onTouchUp(context, document, cx, cy, dx, dy, force); 753 } 754 if (op instanceof TouchHandler) { 755 ((TouchHandler) op).onTouchUp(context, document, this, cx, cy, dx, dy); 756 } 757 if (op instanceof TouchExpression) { 758 TouchExpression touchExpression = (TouchExpression) op; 759 touchExpression.updateVariables(context); 760 touchExpression.touchUp(context, cx, cy, dx, dy); 761 } 762 } 763 } 764 765 /** 766 * Touch Cancel handler 767 * 768 * @param context 769 * @param document 770 * @param x 771 * @param y 772 * @param force 773 */ onTouchCancel( RemoteContext context, CoreDocument document, float x, float y, boolean force)774 public void onTouchCancel( 775 RemoteContext context, CoreDocument document, float x, float y, boolean force) { 776 if (!force && !contains(x, y)) { 777 return; 778 } 779 float cx = x - getScrollX(); 780 float cy = y - getScrollY(); 781 for (Operation op : mList) { 782 if (op instanceof Component) { 783 ((Component) op).onTouchCancel(context, document, cx, cy, force); 784 } 785 if (op instanceof TouchHandler) { 786 ((TouchHandler) op).onTouchCancel(context, document, this, cx, cy); 787 } 788 if (op instanceof TouchExpression) { 789 TouchExpression touchExpression = (TouchExpression) op; 790 touchExpression.updateVariables(context); 791 touchExpression.touchUp(context, cx, cy, 0, 0); 792 } 793 } 794 } 795 796 /** 797 * Touch Drag handler 798 * 799 * @param context 800 * @param document 801 * @param x 802 * @param y 803 * @param force 804 */ onTouchDrag( RemoteContext context, CoreDocument document, float x, float y, boolean force)805 public void onTouchDrag( 806 RemoteContext context, CoreDocument document, float x, float y, boolean force) { 807 if (!force && !contains(x, y)) { 808 return; 809 } 810 float cx = x - getScrollX(); 811 float cy = y - getScrollY(); 812 for (Operation op : mList) { 813 if (op instanceof Component) { 814 ((Component) op).onTouchDrag(context, document, cx, cy, force); 815 } 816 if (op instanceof TouchHandler) { 817 ((TouchHandler) op).onTouchDrag(context, document, this, cx, cy); 818 } 819 if (op instanceof TouchExpression) { 820 TouchExpression touchExpression = (TouchExpression) op; 821 touchExpression.updateVariables(context); 822 touchExpression.touchDrag(context, x, y); 823 } 824 } 825 } 826 827 /** 828 * Returns the location of the component relative to the root component 829 * 830 * @param value a 2 dimension float array that will receive the horizontal and vertical position 831 * of the component. 832 * @param forSelf whether the location is for this container or a child, relevant for scrollable 833 * items. 834 */ getLocationInWindow(@onNull float[] value, boolean forSelf)835 public void getLocationInWindow(@NonNull float[] value, boolean forSelf) { 836 value[0] += mX; 837 value[1] += mY; 838 if (mParent != null) { 839 mParent.getLocationInWindow(value, false); 840 } 841 } 842 843 /** 844 * Returns the location of the component relative to the root component 845 * 846 * @param value a 2 dimension float array that will receive the horizontal and vertical position 847 * of the component. 848 */ getLocationInWindow(@onNull float[] value)849 public void getLocationInWindow(@NonNull float[] value) { 850 getLocationInWindow(value, true); 851 } 852 853 @NonNull 854 @Override toString()855 public String toString() { 856 return "COMPONENT(<" 857 + mComponentId 858 + "> " 859 + getClass().getSimpleName() 860 + ") [" 861 + mX 862 + "," 863 + mY 864 + " - " 865 + mWidth 866 + " x " 867 + mHeight 868 + "] " 869 + textContent() 870 + " Visibility (" 871 + Visibility.toString(mVisibility) 872 + ") "; 873 } 874 875 @NonNull getSerializedName()876 protected String getSerializedName() { 877 return "COMPONENT"; 878 } 879 880 @Override serializeToString(int indent, @NonNull StringSerializer serializer)881 public void serializeToString(int indent, @NonNull StringSerializer serializer) { 882 String content = 883 getSerializedName() 884 + " [" 885 + mComponentId 886 + ":" 887 + mAnimationId 888 + "] = " 889 + "[" 890 + mX 891 + ", " 892 + mY 893 + ", " 894 + mWidth 895 + ", " 896 + mHeight 897 + "] " 898 + Visibility.toString(mVisibility); 899 // + " [" + mNeedsMeasure + ", " + mNeedsRepaint + "]" 900 serializer.append(indent, content); 901 } 902 903 @Override write(@onNull WireBuffer buffer)904 public void write(@NonNull WireBuffer buffer) { 905 // nothing 906 } 907 908 /** Returns the top-level RootLayoutComponent */ 909 @NonNull getRoot()910 public RootLayoutComponent getRoot() throws Exception { 911 if (this instanceof RootLayoutComponent) { 912 return (RootLayoutComponent) this; 913 } 914 Component p = mParent; 915 while (!(p instanceof RootLayoutComponent)) { 916 if (p == null) { 917 throw new Exception("No RootLayoutComponent found"); 918 } 919 p = p.mParent; 920 } 921 return (RootLayoutComponent) p; 922 } 923 924 @NonNull 925 @Override deepToString(@onNull String indent)926 public String deepToString(@NonNull String indent) { 927 StringBuilder builder = new StringBuilder(); 928 builder.append(indent); 929 builder.append(toString()); 930 builder.append("\n"); 931 String indent2 = " " + indent; 932 for (Operation op : mList) { 933 builder.append(op.deepToString(indent2)); 934 builder.append("\n"); 935 } 936 return builder.toString(); 937 } 938 939 /** 940 * Mark itself as needing to be remeasured, and walk back up the tree to mark each parents as 941 * well. 942 */ invalidateMeasure()943 public void invalidateMeasure() { 944 needsRepaint(); 945 mNeedsMeasure = true; 946 Component p = mParent; 947 while (p != null) { 948 p.mNeedsMeasure = true; 949 p = p.mParent; 950 } 951 } 952 953 /** Mark the tree as needing a repaint */ needsRepaint()954 public void needsRepaint() { 955 try { 956 getRoot().mNeedsRepaint = true; 957 } catch (Exception e) { 958 // nothing 959 } 960 } 961 962 /** 963 * Debugging function returning the list of child operations 964 * 965 * @return a formatted string with the list of operations 966 */ 967 @NonNull content()968 public String content() { 969 StringBuilder builder = new StringBuilder(); 970 for (Operation op : mList) { 971 builder.append("- "); 972 builder.append(op); 973 builder.append("\n"); 974 } 975 return builder.toString(); 976 } 977 978 /** 979 * Returns a string containing the text operations if any 980 * 981 * @return 982 */ 983 @NonNull textContent()984 public String textContent() { 985 StringBuilder builder = new StringBuilder(); 986 for (Operation ignored : mList) { 987 String letter = ""; 988 // if (op instanceof DrawTextRun) { 989 // letter = "[" + ((DrawTextRun) op).text + "]"; 990 // } 991 builder.append(letter); 992 } 993 return builder.toString(); 994 } 995 996 /** 997 * Utility debug function 998 * 999 * @param component 1000 * @param context 1001 */ debugBox(@onNull Component component, @NonNull PaintContext context)1002 public void debugBox(@NonNull Component component, @NonNull PaintContext context) { 1003 float width = component.mWidth; 1004 float height = component.mHeight; 1005 1006 context.savePaint(); 1007 mPaint.reset(); 1008 mPaint.setColor(0, 0, 255, 255); // Blue color 1009 context.applyPaint(mPaint); 1010 context.drawLine(0f, 0f, width, 0f); 1011 context.drawLine(width, 0f, width, height); 1012 context.drawLine(width, height, 0f, height); 1013 context.drawLine(0f, height, 0f, 0f); 1014 // context.setColor(255, 0, 0, 255) 1015 // context.drawLine(0f, 0f, width, height) 1016 // context.drawLine(0f, height, width, 0f) 1017 context.restorePaint(); 1018 } 1019 1020 /** 1021 * Set the position of this component relative to its parent 1022 * 1023 * @param x horizontal position 1024 * @param y vertical position 1025 */ setLayoutPosition(float x, float y)1026 public void setLayoutPosition(float x, float y) { 1027 this.mX = x; 1028 this.mY = y; 1029 } 1030 1031 /** 1032 * The vertical position of this component relative to its parent 1033 * 1034 * @return 1035 */ getTranslateX()1036 public float getTranslateX() { 1037 if (mParent != null) { 1038 return mX - mParent.mX; 1039 } 1040 return 0f; 1041 } 1042 1043 /** 1044 * The horizontal position of this component relative to its parent 1045 * 1046 * @return 1047 */ getTranslateY()1048 public float getTranslateY() { 1049 if (mParent != null) { 1050 return mY - mParent.mY; 1051 } 1052 return 0f; 1053 } 1054 1055 /** 1056 * Paint the component itself. 1057 * 1058 * @param context 1059 */ paintingComponent(@onNull PaintContext context)1060 public void paintingComponent(@NonNull PaintContext context) { 1061 if (mPreTranslate != null) { 1062 mPreTranslate.paint(context); 1063 } 1064 Component prev = context.getContext().mLastComponent; 1065 context.getContext().mLastComponent = this; 1066 context.save(); 1067 context.translate(mX, mY); 1068 if (context.isVisualDebug()) { 1069 debugBox(this, context); 1070 } 1071 for (Operation op : mList) { 1072 if (op.isDirty() && op instanceof VariableSupport) { 1073 ((VariableSupport) op).updateVariables(context.getContext()); 1074 op.markNotDirty(); 1075 } 1076 if (op instanceof PaintOperation) { 1077 ((PaintOperation) op).paint(context); 1078 context.getContext().incrementOpCount(); 1079 } else { 1080 op.apply(context.getContext()); 1081 context.getContext().incrementOpCount(); 1082 } 1083 } 1084 context.restore(); 1085 context.getContext().mLastComponent = prev; 1086 } 1087 1088 /** 1089 * If animation is turned on and we need to be animated, we'll apply it. 1090 * 1091 * @param context 1092 * @return 1093 */ applyAnimationAsNeeded(@onNull PaintContext context)1094 public boolean applyAnimationAsNeeded(@NonNull PaintContext context) { 1095 if (context.isAnimationEnabled() && mAnimateMeasure != null) { 1096 mAnimateMeasure.paint(context); 1097 if (mAnimateMeasure.isDone()) { 1098 mAnimateMeasure = null; 1099 clearNeedsBoundsAnimation(); 1100 needsRepaint(); 1101 } else { 1102 markNeedsBoundsAnimation(); 1103 } 1104 return true; 1105 } 1106 return false; 1107 } 1108 1109 @Override paint(@onNull PaintContext context)1110 public void paint(@NonNull PaintContext context) { 1111 if (context.isVisualDebug()) { 1112 context.save(); 1113 context.translate(mX, mY); 1114 context.savePaint(); 1115 mPaint.reset(); 1116 mPaint.setColor(0, 255, 0, 255); // Green 1117 context.applyPaint(mPaint); 1118 context.drawLine(0f, 0f, mWidth, 0f); 1119 context.drawLine(mWidth, 0f, mWidth, mHeight); 1120 context.drawLine(mWidth, mHeight, 0f, mHeight); 1121 context.drawLine(0f, mHeight, 0f, 0f); 1122 mPaint.setColor(255, 0, 0, 255); // Red 1123 context.applyPaint(mPaint); 1124 context.drawLine(0f, 0f, mWidth, mHeight); 1125 context.drawLine(0f, mHeight, mWidth, 0f); 1126 context.restorePaint(); 1127 context.restore(); 1128 } 1129 if (applyAnimationAsNeeded(context)) { 1130 return; 1131 } 1132 if (isGone() || isInvisible()) { 1133 return; 1134 } 1135 paintingComponent(context); 1136 } 1137 1138 /** 1139 * Extract child components 1140 * 1141 * @param components an ArrayList that will be populated by child components (if any) 1142 */ getComponents(@onNull ArrayList<Component> components)1143 public void getComponents(@NonNull ArrayList<Component> components) { 1144 for (Operation op : mList) { 1145 if (op instanceof Component) { 1146 components.add((Component) op); 1147 } 1148 } 1149 } 1150 1151 /** 1152 * Extract child Data elements 1153 * 1154 * @param data an ArrayList that will be populated with the Data elements (if any) 1155 */ getData(@onNull ArrayList<Operation> data)1156 public void getData(@NonNull ArrayList<Operation> data) { 1157 for (Operation op : mList) { 1158 if (op instanceof TextData || op instanceof BitmapData) { 1159 data.add(op); 1160 } 1161 } 1162 } 1163 1164 /** 1165 * Returns the number of children components 1166 * 1167 * @return 1168 */ getComponentCount()1169 public int getComponentCount() { 1170 int count = 0; 1171 for (Operation op : mList) { 1172 if (op instanceof Component) { 1173 count += 1 + ((Component) op).getComponentCount(); 1174 } 1175 } 1176 return count; 1177 } 1178 1179 /** 1180 * Return the id used for painting the component -- either its component id or its animation id 1181 * (if set) 1182 * 1183 * @return 1184 */ getPaintId()1185 public int getPaintId() { 1186 if (mAnimationId != -1) { 1187 return mAnimationId; 1188 } 1189 return mComponentId; 1190 } 1191 1192 /** 1193 * Return true if the needsRepaint flag is set on this component 1194 * 1195 * @return 1196 */ doesNeedsRepaint()1197 public boolean doesNeedsRepaint() { 1198 return mNeedsRepaint; 1199 } 1200 1201 /** 1202 * Utility function to return a component from its id 1203 * 1204 * @param cid 1205 * @return 1206 */ 1207 @Nullable getComponent(int cid)1208 public Component getComponent(int cid) { 1209 if (mComponentId == cid || mAnimationId == cid) { 1210 return this; 1211 } 1212 for (Operation c : mList) { 1213 if (c instanceof Component) { 1214 Component search = ((Component) c).getComponent(cid); 1215 if (search != null) { 1216 return search; 1217 } 1218 } 1219 } 1220 return null; 1221 } 1222 1223 @Override serialize(MapSerializer serializer)1224 public void serialize(MapSerializer serializer) { 1225 serializer.addTags(SerializeTags.COMPONENT); 1226 serializer.addType(getSerializedName()); 1227 serializer.add("id", mComponentId); 1228 serializer.add("x", mX); 1229 serializer.add("y", mY); 1230 serializer.add("width", mWidth); 1231 serializer.add("height", mHeight); 1232 serializer.add("visibility", Visibility.toString(mVisibility)); 1233 serializer.add("list", mList); 1234 } 1235 1236 /** 1237 * Return ourself or a matching modifier. Used by the semantics / accessibility layer. 1238 * 1239 * @param operationClass 1240 * @return 1241 * @param <T> 1242 */ selfOrModifier(Class<T> operationClass)1243 public <T> @Nullable T selfOrModifier(Class<T> operationClass) { 1244 if (operationClass.isInstance(this)) { 1245 return operationClass.cast(this); 1246 } 1247 1248 for (Operation op : mList) { 1249 if (operationClass.isInstance(op)) { 1250 return operationClass.cast(op); 1251 } 1252 } 1253 1254 return null; 1255 } 1256 } 1257