• 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.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