diff options
19 files changed, 1519 insertions, 149 deletions
diff --git a/core/java/com/android/internal/widget/remotecompose/core/Operations.java b/core/java/com/android/internal/widget/remotecompose/core/Operations.java index fc8668e4c657..4b8dbf6365f9 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/Operations.java +++ b/core/java/com/android/internal/widget/remotecompose/core/Operations.java @@ -37,6 +37,7 @@ import com.android.internal.widget.remotecompose.core.operations.DrawTweenPath; import com.android.internal.widget.remotecompose.core.operations.FloatConstant; import com.android.internal.widget.remotecompose.core.operations.FloatExpression; import com.android.internal.widget.remotecompose.core.operations.Header; +import com.android.internal.widget.remotecompose.core.operations.IntegerExpression; import com.android.internal.widget.remotecompose.core.operations.MatrixRestore; import com.android.internal.widget.remotecompose.core.operations.MatrixRotate; import com.android.internal.widget.remotecompose.core.operations.MatrixSave; @@ -54,6 +55,8 @@ import com.android.internal.widget.remotecompose.core.operations.TextFromFloat; import com.android.internal.widget.remotecompose.core.operations.TextMerge; import com.android.internal.widget.remotecompose.core.operations.Theme; import com.android.internal.widget.remotecompose.core.operations.utilities.IntMap; +import com.android.internal.widget.remotecompose.core.types.BooleanConstant; +import com.android.internal.widget.remotecompose.core.types.IntegerConstant; /** * List of operations supported in a RemoteCompose document @@ -109,6 +112,9 @@ public class Operations { public static final int TEXT_MERGE = 136; public static final int NAMED_VARIABLE = 137; public static final int COLOR_CONSTANT = 138; + public static final int DATA_INT = 140; + public static final int DATA_BOOLEAN = 143; + public static final int INTEGER_EXPRESSION = 144; /////////////////////////////////////////====================== public static IntMap<CompanionOperation> map = new IntMap<>(); @@ -153,6 +159,9 @@ public class Operations { map.put(TEXT_MERGE, TextMerge.COMPANION); map.put(NAMED_VARIABLE, NamedVariable.COMPANION); map.put(COLOR_CONSTANT, ColorConstant.COMPANION); + map.put(DATA_INT, IntegerConstant.COMPANION); + map.put(INTEGER_EXPRESSION, IntegerExpression.COMPANION); + map.put(DATA_BOOLEAN, BooleanConstant.COMPANION); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java b/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java index ecd0efceacf3..6d8a44297538 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java +++ b/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java @@ -59,6 +59,16 @@ public abstract class PaintContext { public abstract void drawRect(float left, float top, float right, float bottom); + /** + * this caches the paint to a paint stack + */ + public abstract void savePaint(); + + /** + * This restores the paint form the paint stack + */ + public abstract void restorePaint(); + public abstract void drawRoundRect(float left, float top, float right, @@ -119,6 +129,10 @@ public abstract class PaintContext { float start, float stop); + /** + * This applies changes to the current paint + * @param mPaintData the list of changes + */ public abstract void applyPaint(PaintBundle mPaintData); /** diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java index d462c7d64a5a..f5f155e3ab0b 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java +++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java @@ -37,6 +37,7 @@ import com.android.internal.widget.remotecompose.core.operations.DrawTweenPath; import com.android.internal.widget.remotecompose.core.operations.FloatConstant; import com.android.internal.widget.remotecompose.core.operations.FloatExpression; import com.android.internal.widget.remotecompose.core.operations.Header; +import com.android.internal.widget.remotecompose.core.operations.IntegerExpression; import com.android.internal.widget.remotecompose.core.operations.MatrixRestore; import com.android.internal.widget.remotecompose.core.operations.MatrixRotate; import com.android.internal.widget.remotecompose.core.operations.MatrixSave; @@ -55,6 +56,7 @@ import com.android.internal.widget.remotecompose.core.operations.Theme; import com.android.internal.widget.remotecompose.core.operations.Utils; import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle; import com.android.internal.widget.remotecompose.core.operations.utilities.easing.FloatAnimation; +import com.android.internal.widget.remotecompose.core.types.IntegerConstant; import java.io.File; import java.io.FileInputStream; @@ -876,6 +878,27 @@ public class RemoteComposeBuffer { return Utils.asNan(id); } + + /** + * Add a Integer return an id number pointing to that float. + * @param value + * @return + */ + public int addInteger(int value) { + int id = mRemoteComposeState.cacheInteger(value); + IntegerConstant.COMPANION.apply(mBuffer, id, value); + return id; + } + + /** + * Add a IntegerId as float ID. + * @param id id to be converted + * @return + */ + public float asFloatId(int id) { + return Utils.asNan(id); + } + /** * Add a float that is a computation based on variables * @param value A RPN style float operation i.e. "4, 3, ADD" outputs 7 @@ -901,6 +924,18 @@ public class RemoteComposeBuffer { } /** + * Add and integer expression + * @param mask defines which elements are operators or variables + * @param value array of values to calculate maximum 32 + * @return + */ + public int addIntegerExpression(int mask, int[] value) { + int id = mRemoteComposeState.cache(value); + IntegerExpression.COMPANION.apply(mBuffer, id, mask, value); + return id; + } + + /** * Add a simple color * @param color * @return id that represents that color @@ -1038,5 +1073,6 @@ public class RemoteComposeBuffer { NamedVariable.COMPANION.apply(mBuffer, id, NamedVariable.COLOR_TYPE, name); } + } diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java index bfe67c8e9d19..6b06a544be40 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java +++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java @@ -21,6 +21,8 @@ import static com.android.internal.widget.remotecompose.core.RemoteContext.ID_TI import static com.android.internal.widget.remotecompose.core.RemoteContext.ID_WINDOW_HEIGHT; import static com.android.internal.widget.remotecompose.core.RemoteContext.ID_WINDOW_WIDTH; +import com.android.internal.widget.remotecompose.core.operations.utilities.IntFloatMap; +import com.android.internal.widget.remotecompose.core.operations.utilities.IntIntMap; import com.android.internal.widget.remotecompose.core.operations.utilities.IntMap; import java.util.ArrayList; @@ -37,16 +39,12 @@ public class RemoteComposeState { private final IntMap<Object> mIntDataMap = new IntMap<>(); private final IntMap<Boolean> mIntWrittenMap = new IntMap<>(); private final HashMap<Object, Integer> mDataIntMap = new HashMap(); - private final float[] mFloatMap = new float[MAX_FLOATS]; // efficient cache - private final int[] mColorMap = new int[MAX_COLORS]; // efficient cache + private final IntFloatMap mFloatMap = new IntFloatMap(); // efficient cache + private final IntIntMap mIntegerMap = new IntIntMap(); // efficient cache + private final IntIntMap mColorMap = new IntIntMap(); // efficient cache private final boolean[] mColorOverride = new boolean[MAX_COLORS]; private int mNextId = START_ID; - { - for (int i = 0; i < mFloatMap.length; i++) { - mFloatMap[i] = Float.NaN; - } - } /** * Get Object based on id. The system will cache things like bitmaps @@ -113,29 +111,62 @@ public class RemoteComposeState { */ public int cacheFloat(float item) { int id = nextId(); - mFloatMap[id] = item; + mFloatMap.put(id, item); + mIntegerMap.put(id, (int) item); return id; } /** * Insert an item in the cache */ - public void cacheFloat(int id, float item) { - mFloatMap[id] = item; + public int cacheInteger(int item) { + int id = nextId(); + mIntegerMap.put(id, item); + mFloatMap.put(id, item); + return id; } /** * Insert an item in the cache */ + public void cacheFloat(int id, float item) { + mFloatMap.put(id, item); + } + + /** + * Insert an float item in the cache + */ public void updateFloat(int id, float item) { - mFloatMap[id] = item; + mFloatMap.put(id, item); + mIntegerMap.put(id, (int) item); } /** - * get float + * Insert an integer item in the cache + */ + public void updateInteger(int id, int item) { + mFloatMap.put(id, item); + mIntegerMap.put(id, item); + } + + /** + * get a float from the float cache + * + * @param id of the float value + * @return the float value */ public float getFloat(int id) { - return mFloatMap[id]; + return mFloatMap.get(id); + } + + /** + * get an integer from the cache + * + * @param id of the integer value + * @return the integer + */ + public int getInteger(int id) { + return mIntegerMap.get(id); } /** @@ -145,11 +176,12 @@ public class RemoteComposeState { * @return */ public int getColor(int id) { - return mColorMap[id]; + return mColorMap.get(id); } /** * Modify the color at id. + * * @param id * @param color */ @@ -157,7 +189,7 @@ public class RemoteComposeState { if (mColorOverride[id]) { return; } - mColorMap[id] = color; + mColorMap.put(id, color); } /** @@ -169,7 +201,7 @@ public class RemoteComposeState { */ public void overrideColor(int id, int color) { mColorOverride[id] = true; - mColorMap[id] = color; + mColorMap.put(id, color); } /** @@ -205,6 +237,7 @@ public class RemoteComposeState { /** * Get the next available id + * * @return */ public int nextId() { @@ -213,6 +246,7 @@ public class RemoteComposeState { /** * Set the next id + * * @param id */ public void setNextId(int id) { @@ -234,6 +268,7 @@ public class RemoteComposeState { /** * Commands that listen to variables add themselves. + * * @param id * @param variableSupport */ @@ -243,6 +278,7 @@ public class RemoteComposeState { /** * List of Commands that need to be updated + * * @param context * @return */ @@ -264,6 +300,7 @@ public class RemoteComposeState { /** * Set the width of the overall document on screen. + * * @param width */ public void setWindowWidth(float width) { @@ -272,6 +309,7 @@ public class RemoteComposeState { /** * Set the width of the overall document on screen. + * * @param height */ public void setWindowHeight(float height) { diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java index 32027d8b535c..41eeb5bdd476 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java +++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java @@ -39,6 +39,7 @@ public abstract class RemoteContext { public float mWidth = 0f; public float mHeight = 0f; + private float mAnimationTime; /** * Load a path under an id. @@ -65,11 +66,20 @@ public abstract class RemoteContext { public abstract void loadColor(int id, int color); /** + * Set the animation time allowing the creator to control animation rates + * @param time + */ + public void setAnimationTime(float time) { + mAnimationTime = time; + } + + /** * gets the time animation clock as float in seconds * @return a monotonic time in seconds (arbitrary zero point) */ public float getAnimationTime() { - return (System.nanoTime() - mStart) * 1E-9f; + mAnimationTime = (System.nanoTime() - mStart) * 1E-9f; // Eliminate + return mAnimationTime; } /** @@ -213,6 +223,13 @@ public abstract class RemoteContext { public abstract void loadFloat(int id, float value); /** + * Load a float + * @param id + * @param value + */ + public abstract void loadInteger(int id, int value); + + /** * Load an animated float associated with an id * Todo: Remove? * @param id @@ -235,6 +252,13 @@ public abstract class RemoteContext { public abstract float getFloat(int id); /** + * Get a float given an id + * @param id + * @return + */ + public abstract int getInteger(int id); + + /** * Get the color given and ID * @param id * @return diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java index 56b2f1f7bb86..5a4a9f3754df 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java @@ -63,23 +63,23 @@ public abstract class DrawBase3 extends PaintOperation @Override public void updateVariables(RemoteContext context) { - mV1 = (Float.isNaN(mValue1)) + mV1 = (Utils.isVariable(mValue1)) ? context.getFloat(Utils.idFromNan(mValue1)) : mValue1; - mV2 = (Float.isNaN(mValue2)) + mV2 = (Utils.isVariable(mValue2)) ? context.getFloat(Utils.idFromNan(mValue2)) : mValue2; - mV3 = (Float.isNaN(mValue3)) + mV3 = (Utils.isVariable(mValue3)) ? context.getFloat(Utils.idFromNan(mValue3)) : mValue3; } @Override public void registerListening(RemoteContext context) { - if (Float.isNaN(mValue1)) { + if (Utils.isVariable(mValue1)) { context.listensTo(Utils.idFromNan(mValue1), this); } - if (Float.isNaN(mValue2)) { + if (Utils.isVariable(mValue2)) { context.listensTo(Utils.idFromNan(mValue2), this); } - if (Float.isNaN(mValue3)) { + if (Utils.isVariable(mValue3)) { context.listensTo(Utils.idFromNan(mValue3), this); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextRun.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextRun.java deleted file mode 100644 index a0992528d981..000000000000 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextRun.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.internal.widget.remotecompose.core.operations; - -import com.android.internal.widget.remotecompose.core.CompanionOperation; -import com.android.internal.widget.remotecompose.core.Operation; -import com.android.internal.widget.remotecompose.core.Operations; -import com.android.internal.widget.remotecompose.core.PaintContext; -import com.android.internal.widget.remotecompose.core.PaintOperation; -import com.android.internal.widget.remotecompose.core.WireBuffer; - -import java.util.List; - -public class DrawTextRun extends PaintOperation { - public static final Companion COMPANION = new Companion(); - int mTextID; - int mStart = 0; - int mEnd = 0; - int mContextStart = 0; - int mContextEnd = 0; - float mX = 0f; - float mY = 0f; - boolean mRtl = false; - - public DrawTextRun(int textID, - int start, - int end, - int contextStart, - int contextEnd, - float x, - float y, - boolean rtl) { - mTextID = textID; - mStart = start; - mEnd = end; - mContextStart = contextStart; - mContextEnd = contextEnd; - mX = x; - mY = y; - mRtl = rtl; - } - - @Override - public void write(WireBuffer buffer) { - COMPANION.apply(buffer, mTextID, mStart, mEnd, mContextStart, mContextEnd, mX, mY, mRtl); - - } - - @Override - public String toString() { - return ""; - } - - public static class Companion implements CompanionOperation { - private Companion() { - } - - @Override - public void read(WireBuffer buffer, List<Operation> operations) { - int text = buffer.readInt(); - int start = buffer.readInt(); - int end = buffer.readInt(); - int contextStart = buffer.readInt(); - int contextEnd = buffer.readInt(); - float x = buffer.readFloat(); - float y = buffer.readFloat(); - boolean rtl = buffer.readBoolean(); - DrawTextRun op = new DrawTextRun(text, start, end, contextStart, contextEnd, x, y, rtl); - - operations.add(op); - } - - @Override - public String name() { - return ""; - } - - @Override - public int id() { - return 0; - } - - public void apply(WireBuffer buffer, - int textID, - int start, - int end, - int contextStart, - int contextEnd, - float x, - float y, - boolean rtl) { - buffer.start(Operations.DRAW_TEXT_RUN); - buffer.writeInt(textID); - buffer.writeInt(start); - buffer.writeInt(end); - buffer.writeInt(contextStart); - buffer.writeInt(contextEnd); - buffer.writeFloat(x); - buffer.writeFloat(y); - buffer.writeBoolean(rtl); - } - } - - @Override - public void paint(PaintContext context) { - context.drawTextRun(mTextID, mStart, mEnd, mContextStart, mContextEnd, mX, mY, mRtl); - } -} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/IntegerExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/IntegerExpression.java new file mode 100644 index 000000000000..d52df5d2bcea --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/IntegerExpression.java @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.widget.remotecompose.core.operations; + +import com.android.internal.widget.remotecompose.core.CompanionOperation; +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.RemoteContext; +import com.android.internal.widget.remotecompose.core.VariableSupport; +import com.android.internal.widget.remotecompose.core.WireBuffer; +import com.android.internal.widget.remotecompose.core.operations.utilities.IntegerExpressionEvaluator; + +import java.util.Arrays; +import java.util.List; + +/** + * Operation to deal with AnimatedFloats + * This is designed to be an optimized calculation for things like + * injecting the width of the component int draw rect + * As well as supporting generalized animation floats. + * The floats represent a RPN style calculator + */ +public class IntegerExpression implements Operation, VariableSupport { + public int mId; + private int mMask; + private int mPreMask; + public int[] mSrcValue; + public int[] mPreCalcValue; + private float mLastChange = Float.NaN; + public static final Companion COMPANION = new Companion(); + public static final int MAX_STRING_SIZE = 4000; + IntegerExpressionEvaluator mExp = new IntegerExpressionEvaluator(); + + public IntegerExpression(int id, int mask, int[] value) { + this.mId = id; + this.mMask = mask; + this.mSrcValue = value; + } + + @Override + public void updateVariables(RemoteContext context) { + if (mPreCalcValue == null || mPreCalcValue.length != mSrcValue.length) { + mPreCalcValue = new int[mSrcValue.length]; + } + mPreMask = mMask; + for (int i = 0; i < mSrcValue.length; i++) { + if (isId(mMask, i, mSrcValue[i])) { + mPreMask &= ~(0x1 << i); + mPreCalcValue[i] = context.getInteger(mSrcValue[i]); + } else { + mPreCalcValue[i] = mSrcValue[i]; + } + } + } + + + @Override + public void registerListening(RemoteContext context) { + for (int i = 0; i < mSrcValue.length; i++) { + if (isId(mMask, i, mSrcValue[i])) { + context.listensTo(mSrcValue[i], this); + } + } + } + + @Override + public void apply(RemoteContext context) { + updateVariables(context); + float t = context.getAnimationTime(); + if (Float.isNaN(mLastChange)) { + mLastChange = t; + } + int v = mExp.eval(mPreMask, Arrays.copyOf(mPreCalcValue, mPreCalcValue.length)); + context.loadInteger(mId, v); + } + + @Override + public void write(WireBuffer buffer) { + COMPANION.apply(buffer, mId, mMask, mSrcValue); + } + + @Override + public String toString() { + StringBuilder s = new StringBuilder(); + for (int i = 0; i < mPreCalcValue.length; i++) { + if (i != 0) { + s.append(" "); + } + if (IntegerExpressionEvaluator.isOperation(mMask, i)) { + if (isId(mMask, i, mSrcValue[i])) { + s.append("[" + mSrcValue[i] + "]"); + } else { + s.append(IntegerExpressionEvaluator.toMathName(mPreCalcValue[i])); + } + } else { + s.append(mSrcValue[i]); + } + } + return "IntegerExpression[" + mId + "] = (" + s + ")"; + } + + public static class Companion implements CompanionOperation { + private Companion() { + } + + @Override + public String name() { + return "FloatExpression"; + } + + @Override + public int id() { + return Operations.INTEGER_EXPRESSION; + } + + /** + * Writes out the operation to the buffer + * + * @param buffer + * @param id + * @param mask + * @param value + */ + public void apply(WireBuffer buffer, int id, int mask, int[] value) { + buffer.start(Operations.INTEGER_EXPRESSION); + buffer.writeInt(id); + buffer.writeInt(mask); + buffer.writeInt(value.length); + for (int i = 0; i < value.length; i++) { + buffer.writeInt(value[i]); + } + } + + @Override + public void read(WireBuffer buffer, List<Operation> operations) { + int id = buffer.readInt(); + int mask = buffer.readInt(); + int len = buffer.readInt(); + + int[] values = new int[len]; + for (int i = 0; i < values.length; i++) { + values[i] = buffer.readInt(); + } + + operations.add(new IntegerExpression(id, mask, values)); + } + } + + @Override + public String deepToString(String indent) { + return indent + toString(); + } + + /** + * given the "i" position in the mask is this an ID + * @param mask 32 bit mask used for defining numbers vs other + * @param i the bit in question + * @param value the value + * @return true if this is an ID + */ + public static boolean isId(int mask, int i, int value) { + return ((1 << i) & mask) != 0 && value < IntegerExpressionEvaluator.OFFSET; + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java b/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java index fcb3bfaca503..e9b0c3b11044 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java @@ -40,7 +40,7 @@ public class Utils { * @param n * @return */ - static String trimString(String str, int n) { + public static String trimString(String str, int n) { if (str.length() > n) { str = str.substring(0, n - 3) + "..."; } @@ -55,6 +55,9 @@ public class Utils { */ public static String floatToString(float idvalue, float value) { if (Float.isNaN(idvalue)) { + if (idFromNan(value) == 0) { + return "NaN"; + } return "[" + idFromNan(idvalue) + "]" + floatToString(value); } return floatToString(value); @@ -67,6 +70,9 @@ public class Utils { */ public static String floatToString(float value) { if (Float.isNaN(value)) { + if (idFromNan(value) == 0) { + return "NaN"; + } return "[" + idFromNan(value) + "]"; } return Float.toString(value); @@ -107,6 +113,7 @@ public class Utils { public static boolean isVariable(float v) { if (Float.isNaN(v)) { int id = idFromNan(v); + if (id == 0) return false; return id > 40 || id < 10; } return false; @@ -222,6 +229,4 @@ public class Utils { } return 0; } - - } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/Painter.java b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/Painter.java new file mode 100644 index 000000000000..ada3757bd4ca --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/Painter.java @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.widget.remotecompose.core.operations.paint; + + +/** + * Provides a Builder pattern for a PaintBundle + */ +class Painter { + PaintBundle mPaint; + + /** + * Write the paint to the buffer + */ + public PaintBundle commit() { + return mPaint; + } + + public Painter setAntiAlias(boolean aa) { + mPaint.setAntiAlias(aa); + return this; + } + + public Painter setColor(int color) { + mPaint.setColor(color); + return this; + } + + public Painter setColorId(int colorId) { + mPaint.setColorId(colorId); + return this; + } + + /** + * Set the paint's Join. + * + * @param join set the paint's Join, used whenever the paint's style is + * Stroke or StrokeAndFill. + */ + public Painter setStrokeJoin(int join) { + mPaint.setStrokeJoin(join); + return this; + } + + /** + * Set the width for stroking. Pass 0 to stroke in hairline mode. + * Hairlines always draws a single + * pixel independent of the canvas's matrix. + * + * @param width set the paint's stroke width, used whenever the paint's + * style is Stroke or StrokeAndFill. + */ + public Painter setStrokeWidth(float width) { + mPaint.setStrokeWidth(width); + return this; + } + + /** + * Set the paint's style, used for controlling how primitives' geometries + * are interpreted (except for drawBitmap, which always assumes Fill). + * + * @param style The new style to set in the paint + */ + public Painter setStyle(int style) { + mPaint.setStyle(style); + return this; + } + + /** + * Set the paint's Cap. + * + * @param cap set the paint's line cap style, used whenever the paint's + * style is Stroke or StrokeAndFill. + */ + public Painter setStrokeCap(int cap) { + mPaint.setStrokeCap(cap); + return this; + } + + /** + * Set the paint's stroke miter value. This is used to control the behavior + * of miter joins when the joins angle is sharp. This value must be >= 0. + * + * @param miter set the miter limit on the paint, used whenever the paint's + * style is Stroke or StrokeAndFill. + */ + public Painter setStrokeMiter(float miter) { + mPaint.setStrokeMiter(miter); + return this; + } + + /** + * Helper to setColor(), that only assigns the color's alpha value, + * leaving its r,g,b values unchanged. Results are undefined if the alpha + * value is outside of the range [0..1.0] + * + * @param alpha set the alpha component [0..1.0] of the paint's color. + */ + public Painter setAlpha(float alpha) { + mPaint.setAlpha((alpha > 2) ? alpha / 255f : alpha); + return this; + } + + /** + * Create a color filter that uses the specified color and Porter-Duff mode. + * + * @param color The ARGB source color used with the specified Porter-Duff + * mode + * @param mode The porter-duff mode that is applied + */ + public Painter setPorterDuffColorFilter(int color, int mode) { + mPaint.setColorFilter(color, mode); + return this; + } + + /** + * sets a shader that draws a linear gradient along a line. + * + * @param startX The x-coordinate for the start of the gradient line + * @param startY The y-coordinate for the start of the gradient line + * @param endX The x-coordinate for the end of the gradient line + * @param endY The y-coordinate for the end of the gradient line + * @param colors The sRGB colors to be distributed along the gradient + * line + * @param positions May be null. The relative positions [0..1] of each + * corresponding color in the colors array. If this is null, + * the colors are distributed evenly along the gradient + * line. + * @param tileMode The Shader tiling mode + */ + public Painter setLinearGradient( + float startX, + float startY, + float endX, + float endY, + int[] colors, + float[] positions, + int tileMode + ) { + mPaint.setLinearGradient(colors, positions, startX, + startY, endX, endY, tileMode); + return this; + } + + /** + * Sets a shader that draws a radial gradient given the center and radius. + * + * @param centerX The x-coordinate of the center of the radius + * @param centerY The y-coordinate of the center of the radius + * @param radius Must be positive. The radius of the circle for this + * gradient. + * @param colors The sRGB colors to be distributed between the center + * and edge of the circle + * @param positions May be <code>null</code>. Valid values are between + * <code>0.0f</code> and + * <code>1.0f</code>. The relative position of each + * corresponding color in the colors array. If + * <code>null</code>, colors are distributed evenly + * between the center and edge of the circle. + * @param tileMode The Shader tiling mode + */ + public Painter setRadialGradient( + float centerX, + float centerY, + float radius, + int[] colors, + float[] positions, + int tileMode + ) { + mPaint.setRadialGradient(colors, positions, centerX, + centerY, radius, tileMode); + return this; + } + + /** + * Set a shader that draws a sweep gradient around a center point. + * + * @param centerX The x-coordinate of the center + * @param centerY The y-coordinate of the center + * @param colors The sRGB colors to be distributed between around the + * center. There must be at least 2 colors in the array. + * @param positions May be NULL. The relative position of each corresponding + * color in the colors array, beginning with 0 and ending + * with 1.0. If the values are not monotonic, the drawing + * may produce unexpected results. If positions is NULL, + * then the colors are automatically spaced evenly. + */ + public Painter setSweepGradient( + float centerX, + float centerY, + int[] colors, + float[] positions + ) { + mPaint.setSweepGradient(colors, positions, centerX, centerY); + return this; + } + + /** + * Set the paint's text size. This value must be > 0 + * + * @param size set the paint's text size in pixel units. + */ + public Painter setTextSize(float size) { + mPaint.setTextSize(size); + return this; + } + + /** + * sets a typeface object that best matches the specified existing + * typeface and the specified weight and italic style + * + * <p>Below are numerical values and corresponding common weight names.</p> + * <table> <thead> + * <tr><th>Value</th><th>Common weight name</th></tr> </thead> <tbody> + * <tr><td>100</td><td>Thin</td></tr> + * <tr><td>200</td><td>Extra Light</td></tr> + * <tr><td>300</td><td>Light</td></tr> + * <tr><td>400</td><td>Normal</td></tr> + * <tr><td>500</td><td>Medium</td></tr> + * <tr><td>600</td><td>Semi Bold</td></tr> + * <tr><td>700</td><td>Bold</td></tr> + * <tr><td>800</td><td>Extra Bold</td></tr> + * <tr><td>900</td><td>Black</td></tr> </tbody> </table> + * + * @param fontType 0 = default 1 = sans serif 2 = serif 3 = monospace + * @param weight The desired weight to be drawn. + * @param italic {@code true} if italic style is desired to be drawn. + * Otherwise, {@code false} + */ + public Painter setTypeface(int fontType, int weight, boolean italic) { + mPaint.setTextStyle(fontType, weight, italic); + return this; + } + + + public Painter setFilterBitmap(boolean filter) { + mPaint.setFilterBitmap(filter); + return this; + } + + + public Painter setShader(int id) { + mPaint.setShader(id); + return this; + } + +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntFloatMap.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntFloatMap.java new file mode 100644 index 000000000000..23c3ec593b3c --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntFloatMap.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.widget.remotecompose.core.operations.utilities; + +import java.util.Arrays; + +public class IntFloatMap { + + private static final int DEFAULT_CAPACITY = 16; + private static final float LOAD_FACTOR = 0.75f; + private static final int NOT_PRESENT = Integer.MIN_VALUE; + private int[] mKeys; + private float[] mValues; + int mSize; + + public IntFloatMap() { + mKeys = new int[DEFAULT_CAPACITY]; + Arrays.fill(mKeys, NOT_PRESENT); + mValues = new float[DEFAULT_CAPACITY]; + } + + /** + * clear the map + */ + public void clear() { + Arrays.fill(mKeys, NOT_PRESENT); + Arrays.fill(mValues, Float.NaN); // not strictly necessary but defensive + mSize = 0; + } + + /** + * is the key contained in map + * + * @param key the key to check + * @return true if the map contains the key + */ + public boolean contains(int key) { + return findKey(key) != -1; + } + + /** + * Put a item in the map + * + * @param key item'values key + * @param value item's value + * @return old value if exist + */ + public float put(int key, float value) { + if (key == NOT_PRESENT) { + throw new IllegalArgumentException("Key cannot be NOT_PRESENT"); + } + if (mSize > mKeys.length * LOAD_FACTOR) { + resize(); + } + return insert(key, value); + } + + /** + * get an element given the key + * + * @param key the key to fetch + * @return the value + */ + public float get(int key) { + int index = findKey(key); + if (index == -1) { + return 0; + } else + return mValues[index]; + } + + /** + * how many elements in the map + * + * @return number of elements + */ + public int size() { + return mSize; + } + + private float insert(int key, float value) { + int index = hash(key) % mKeys.length; + while (mKeys[index] != NOT_PRESENT && mKeys[index] != key) { + index = (index + 1) % mKeys.length; + } + float oldValue = 0; + if (mKeys[index] == NOT_PRESENT) { + mSize++; + } else { + oldValue = mValues[index]; + } + mKeys[index] = key; + mValues[index] = value; + return oldValue; + } + + private int findKey(int key) { + int index = hash(key) % mKeys.length; + while (mKeys[index] != NOT_PRESENT) { + if (mKeys[index] == key) { + return index; + } + index = (index + 1) % mKeys.length; + } + return -1; + } + + private int hash(int key) { + return key; + } + + private void resize() { + int[] oldKeys = mKeys; + float[] oldValues = mValues; + mKeys = new int[(oldKeys.length * 2)]; + for (int i = 0; i < mKeys.length; i++) { + mKeys[i] = NOT_PRESENT; + } + mValues = new float[oldKeys.length * 2]; + mSize = 0; + for (int i = 0; i < oldKeys.length; i++) { + if (oldKeys[i] != NOT_PRESENT) { + put(oldKeys[i], oldValues[i]); + } + } + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntIntMap.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntIntMap.java new file mode 100644 index 000000000000..221014c9049e --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntIntMap.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.widget.remotecompose.core.operations.utilities; + +import java.util.Arrays; + +public class IntIntMap { + private static final int DEFAULT_CAPACITY = 16; + private static final float LOAD_FACTOR = 0.75f; + private static final int NOT_PRESENT = Integer.MIN_VALUE; + private int[] mKeys; + private int[] mValues; + int mSize; + + public IntIntMap() { + mKeys = new int[DEFAULT_CAPACITY]; + Arrays.fill(mKeys, NOT_PRESENT); + mValues = new int[DEFAULT_CAPACITY]; + } + + /** + * clear the map + */ + public void clear() { + Arrays.fill(mKeys, NOT_PRESENT); + Arrays.fill(mValues, 0); + mSize = 0; + } + + /** + * is the key contained in map + * + * @param key the key to check + * @return true if the map contains the key + */ + public boolean contains(int key) { + return findKey(key) != -1; + } + + /** + * Put a item in the map + * + * @param key item'values key + * @param value item's value + * @return old value if exist + */ + public int put(int key, int value) { + if (key == NOT_PRESENT) { + throw new IllegalArgumentException("Key cannot be NOT_PRESENT"); + } + if (mSize > mKeys.length * LOAD_FACTOR) { + resize(); + } + return insert(key, value); + } + + /** + * get an element given the key + * + * @param key the key to fetch + * @return the value + */ + public int get(int key) { + int index = findKey(key); + if (index == -1) { + return 0; + } else + return mValues[index]; + } + + /** + * how many elements in the map + * + * @return number of elements + */ + public int size() { + return mSize; + } + + private int insert(int key, int value) { + int index = hash(key) % mKeys.length; + while (mKeys[index] != NOT_PRESENT && mKeys[index] != key) { + index = (index + 1) % mKeys.length; + } + int oldValue = 0; + if (mKeys[index] == NOT_PRESENT) { + mSize++; + } else { + oldValue = mValues[index]; + } + mKeys[index] = key; + mValues[index] = value; + return oldValue; + } + + private int findKey(int key) { + int index = hash(key) % mKeys.length; + while (mKeys[index] != NOT_PRESENT) { + if (mKeys[index] == key) { + return index; + } + index = (index + 1) % mKeys.length; + } + return -1; + } + + private int hash(int key) { + return key; + } + + private void resize() { + int[] oldKeys = mKeys; + int[] oldValues = mValues; + mKeys = new int[(oldKeys.length * 2)]; + for (int i = 0; i < mKeys.length; i++) { + mKeys[i] = NOT_PRESENT; + } + mValues = new int[oldKeys.length * 2]; + mSize = 0; + for (int i = 0; i < oldKeys.length; i++) { + if (oldKeys[i] != NOT_PRESENT) { + put(oldKeys[i], oldValues[i]); + } + } + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntegerExpressionEvaluator.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntegerExpressionEvaluator.java new file mode 100644 index 000000000000..4c1389c5a7df --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntegerExpressionEvaluator.java @@ -0,0 +1,429 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.widget.remotecompose.core.operations.utilities; + +/** + * High performance Integer expression evaluator + */ +public class IntegerExpressionEvaluator { + static IntMap<String> sNames = new IntMap<>(); + public static final int OFFSET = 0x10000; + // add, sub, mul,div,mod,min,max, shl, shr, ushr, OR, AND , XOR, COPY_SIGN + public static final int I_ADD = OFFSET + 1; + public static final int I_SUB = OFFSET + 2; + public static final int I_MUL = OFFSET + 3; + public static final int I_DIV = OFFSET + 4; + public static final int I_MOD = OFFSET + 5; + public static final int I_SHL = OFFSET + 6; + public static final int I_SHR = OFFSET + 7; + public static final int I_USHR = OFFSET + 8; + public static final int I_OR = OFFSET + 9; + public static final int I_AND = OFFSET + 10; + public static final int I_XOR = OFFSET + 11; + public static final int I_COPY_SIGN = OFFSET + 12; + public static final int I_MIN = OFFSET + 13; + public static final int I_MAX = OFFSET + 14; + + public static final int I_NEG = OFFSET + 15; + public static final int I_ABS = OFFSET + 16; + public static final int I_INCR = OFFSET + 17; + public static final int I_DECR = OFFSET + 18; + public static final int I_NOT = OFFSET + 19; + public static final int I_SIGN = OFFSET + 20; + + public static final int I_CLAMP = OFFSET + 21; + public static final int I_IFELSE = OFFSET + 22; + public static final int I_MAD = OFFSET + 23; + + public static final float LAST_OP = 24; + + public static final int I_VAR1 = OFFSET + 24; + public static final int I_VAR2 = OFFSET + 24; + + + int[] mStack; + int[] mLocalStack = new int[128]; + int[] mVar; + + + interface Op { + int eval(int sp); + } + + /** + * Evaluate a float expression + * + * @param exp + * @param var + * @return + */ + public int eval(int mask, int[] exp, int... var) { + mStack = exp; + mVar = var; + int sp = -1; + for (int i = 0; i < mStack.length; i++) { + int v = mStack[i]; + if (((1 << i) & mask) != 0) { + sp = mOps[v - OFFSET].eval(sp); + } else { + mStack[++sp] = v; + } + } + return mStack[sp]; + } + + /** + * Evaluate a int expression + * + * @param exp + * @param len + * @param var + * @return + */ + public int eval(int mask, int[] exp, int len, int... var) { + System.arraycopy(exp, 0, mLocalStack, 0, len); + mStack = mLocalStack; + mVar = var; + int sp = -1; + for (int i = 0; i < len; i++) { + int v = mStack[i]; + if (((1 << i) & mask) != 0) { + sp = mOps[v - OFFSET].eval(sp); + } else { + mStack[++sp] = v; + } + } + return mStack[sp]; + } + + /** + * Evaluate a int expression + * + * @param exp + * @param var + * @return + */ + public int evalDB(int mask, int[] exp, int... var) { + mStack = exp; + mVar = var; + int sp = -1; + for (int i = 0; i < exp.length; i++) { + int v = mStack[i]; + if (((1 << i) & mask) != 0) { + System.out.print(" " + sNames.get((v - OFFSET))); + sp = mOps[v - OFFSET].eval(sp); + } else { + System.out.print(" " + v); + mStack[++sp] = v; + } + } + return mStack[sp]; + } + + Op[] mOps = { + null, + (sp) -> { // ADD + mStack[sp - 1] = mStack[sp - 1] + mStack[sp]; + return sp - 1; + }, + (sp) -> { // SUB + mStack[sp - 1] = mStack[sp - 1] - mStack[sp]; + return sp - 1; + }, + (sp) -> { // MUL + mStack[sp - 1] = mStack[sp - 1] * mStack[sp]; + return sp - 1; + }, + (sp) -> { // DIV + mStack[sp - 1] = mStack[sp - 1] / mStack[sp]; + return sp - 1; + }, + (sp) -> { // MOD + mStack[sp - 1] = mStack[sp - 1] % mStack[sp]; + return sp - 1; + }, + (sp) -> { // SHL shift left + mStack[sp - 1] = mStack[sp - 1] << mStack[sp]; + return sp - 1; + }, + (sp) -> { // SHR shift right + mStack[sp - 1] = mStack[sp - 1] >> mStack[sp]; + return sp - 1; + }, + (sp) -> { // USHR unsigned shift right + mStack[sp - 1] = mStack[sp - 1] >>> mStack[sp]; + return sp - 1; + }, + (sp) -> { // OR operator + mStack[sp - 1] = mStack[sp - 1] | mStack[sp]; + return sp - 1; + }, + (sp) -> { // AND operator + mStack[sp - 1] = mStack[sp - 1] & mStack[sp]; + return sp - 1; + }, + (sp) -> { // XOR xor operator + mStack[sp - 1] = mStack[sp - 1] ^ mStack[sp]; + return sp - 1; + }, + (sp) -> { // COPY_SIGN copy the sing of (using bit magic) + mStack[sp - 1] = (mStack[sp - 1] ^ (mStack[sp] >> 31)) + - (mStack[sp] >> 31); + return sp - 1; + }, + (sp) -> { // MIN + mStack[sp - 1] = Math.min(mStack[sp - 1], mStack[sp]); + return sp - 1; + }, + (sp) -> { // MAX + mStack[sp - 1] = Math.max(mStack[sp - 1], mStack[sp]); + return sp - 1; + }, + (sp) -> { // NEG + mStack[sp] = -mStack[sp]; + return sp; + }, + (sp) -> { // ABS + mStack[sp] = Math.abs(mStack[sp]); + return sp; + }, + (sp) -> { // INCR increment + mStack[sp] = mStack[sp] + 1; + return sp; + }, + (sp) -> { // DECR decrement + mStack[sp] = mStack[sp] - 1; + return sp; + }, + (sp) -> { // NOT Bit invert + mStack[sp] = ~mStack[sp]; + return sp; + }, + (sp) -> { // SIGN x<0 = -1,x==0 = 0 , x>0 = 1 + mStack[sp] = (mStack[sp] >> 31) | (-mStack[sp] >>> 31); + return sp; + }, + + (sp) -> { // CLAMP(min,max, val) + mStack[sp - 2] = Math.min(Math.max(mStack[sp - 2], mStack[sp]), + mStack[sp - 1]); + return sp - 2; + }, + (sp) -> { // Ternary conditional + mStack[sp - 2] = (mStack[sp] > 0) + ? mStack[sp - 1] : mStack[sp - 2]; + return sp - 2; + }, + (sp) -> { // MAD + mStack[sp - 2] = mStack[sp] + mStack[sp - 1] * mStack[sp - 2]; + return sp - 2; + }, + + (sp) -> { // first var = + mStack[sp] = mVar[0]; + return sp; + }, + (sp) -> { // second var y? + mStack[sp] = mVar[1]; + return sp; + }, + (sp) -> { // 3rd var z? + mStack[sp] = mVar[2]; + return sp; + }, + }; + + static { + int k = 0; + sNames.put(k++, "NOP"); + sNames.put(k++, "+"); + sNames.put(k++, "-"); + sNames.put(k++, "*"); + sNames.put(k++, "/"); + sNames.put(k++, "%"); + sNames.put(k++, "<<"); + sNames.put(k++, ">>"); + sNames.put(k++, ">>>"); + sNames.put(k++, "|"); + sNames.put(k++, "&"); + sNames.put(k++, "^"); + sNames.put(k++, "copySign"); + sNames.put(k++, "min"); + sNames.put(k++, "max"); + sNames.put(k++, "neg"); + sNames.put(k++, "abs"); + sNames.put(k++, "incr"); + sNames.put(k++, "decr"); + sNames.put(k++, "not"); + sNames.put(k++, "sign"); + sNames.put(k++, "clamp"); + sNames.put(k++, "ifElse"); + sNames.put(k++, "mad"); + sNames.put(k++, "ceil"); + sNames.put(k++, "a[0]"); + sNames.put(k++, "a[1]"); + sNames.put(k++, "a[2]"); + } + + /** + * given a int command return its math name (e.g sin, cos etc.) + * + * @param f + * @return + */ + public static String toMathName(int f) { + int id = f - OFFSET; + return sNames.get(id); + } + + /** + * Convert an expression encoded as an array of ints int ot a string + * + * @param exp + * @param labels + * @return + */ + public static String toString(int mask, int[] exp, String[] labels) { + StringBuilder s = new StringBuilder(); + for (int i = 0; i < exp.length; i++) { + int v = exp[i]; + + if (((1 << i) & mask) != 0) { + if (v < OFFSET) { + s.append(toMathName(v)); + } else { + s.append("["); + s.append(v); + s.append("]"); + } + } else { + if (labels[i] != null) { + s.append(labels[i]); + } + s.append(v); + } + s.append(" "); + } + return s.toString(); + } + + /** + * Convert an expression encoded as an array of ints int ot a string + * + * @param mask bit mask of operators vs commands + * @param exp + * @return + */ + public static String toString(int mask, int[] exp) { + StringBuilder s = new StringBuilder(); + s.append(Integer.toBinaryString(mask)); + s.append(" : "); + for (int i = 0; i < exp.length; i++) { + int v = exp[i]; + + if (((1 << i) & mask) != 0) { + if (v > OFFSET) { + s.append(" "); + s.append(toMathName(v)); + s.append(" "); + + } else { + s.append("["); + s.append(v); + s.append("]"); + } + } + s.append(" " + v); + } + return s.toString(); + } + + /** + * This creates an infix string expression + * @param mask The bits that are operators + * @param exp the array of expressions + * @return infix string + */ + public static String toStringInfix(int mask, int[] exp) { + return toString(mask, exp, exp.length - 1); + } + + static String toString(int mask, int[] exp, int sp) { + String[] str = new String[exp.length]; + if (((1 << sp) & mask) != 0) { + int id = exp[sp] - OFFSET; + switch (NO_OF_OPS[id]) { + case -1: + return "nop"; + case 1: + return sNames.get(id) + "(" + toString(mask, exp, sp - 1) + ") "; + case 2: + if (infix(id)) { + return "(" + toString(mask, exp, sp - 2) + + " " + sNames.get(id) + " " + + toString(mask, exp, sp - 1) + ") "; + } else { + return sNames.get(id) + "(" + + toString(mask, exp, sp - 2) + ", " + + toString(mask, exp, sp - 1) + ")"; + } + case 3: + if (infix(id)) { + return "((" + toString(mask, exp, sp + 3) + ") ? " + + toString(mask, exp, sp - 2) + ":" + + toString(mask, exp, sp - 1) + ")"; + } else { + return sNames.get(id) + + "(" + toString(mask, exp, sp - 3) + + ", " + toString(mask, exp, sp - 2) + + ", " + toString(mask, exp, sp - 1) + ")"; + } + } + } + return Integer.toString(exp[sp]); + } + + static final int[] NO_OF_OPS = { + -1, // no op + 2, 2, 2, 2, 2, // + - * / % + 2, 2, 2, 2, 2, 2, 2, 2, 2, //<<, >> , >>> , | , &, ^, min max + 1, 1, 1, 1, 1, 1, // neg, abs, ++, -- , not , sign + + 3, 3, 3, // clamp, ifElse, mad, + 0, 0, 0 // mad, ?:, + // a[0],a[1],a[2] + }; + + /** + * to be used by parser to determine if command is infix + * + * @param n the operator (minus the offset) + * @return true if the operator is infix + */ + static boolean infix(int n) { + return ((n < 12)); + } + + /** + * is it an id or operation + * @param mask the bits that mark elements as an operation + * @param i the bit to check + * @return true if the bit is 1 + */ + public static boolean isOperation(int mask, int i) { + return ((1 << i) & mask) != 0; + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/types/BooleanConstant.java b/core/java/com/android/internal/widget/remotecompose/core/types/BooleanConstant.java new file mode 100644 index 000000000000..1051192441dd --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/types/BooleanConstant.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.widget.remotecompose.core.types; + +import com.android.internal.widget.remotecompose.core.CompanionOperation; +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.RemoteContext; +import com.android.internal.widget.remotecompose.core.WireBuffer; + +import java.util.List; + +/** + * Used to represent a boolean + */ +public class BooleanConstant implements Operation { + boolean mValue = false; + private int mId; + + public BooleanConstant(int id, boolean value) { + mId = id; + mValue = value; + } + + @Override + public void write(WireBuffer buffer) { + COMPANION.apply(buffer, mId, mValue); + } + + @Override + public void apply(RemoteContext context) { + + } + + @Override + public String deepToString(String indent) { + return toString(); + } + + @Override + public String toString() { + return "BooleanConstant[" + mId + "] = " + mValue + ""; + } + + public static final Companion COMPANION = new Companion(); + + public static class Companion implements CompanionOperation { + private Companion() { + } + + @Override + public String name() { + return "OrigamiBoolean"; + } + + @Override + public int id() { + return Operations.DATA_BOOLEAN; + } + + /** + * Writes out the operation to the buffer + * + * @param buffer + * @param id + * @param value + */ + public void apply(WireBuffer buffer, int id, boolean value) { + buffer.start(Operations.DATA_BOOLEAN); + buffer.writeInt(id); + buffer.writeBoolean(value); + } + + @Override + public void read(WireBuffer buffer, List<Operation> operations) { + int id = buffer.readInt(); + + boolean value = buffer.readBoolean(); + operations.add(new BooleanConstant(id, value)); + } + } + +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java b/core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java new file mode 100644 index 000000000000..ceb323629e98 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.widget.remotecompose.core.types; + +import com.android.internal.widget.remotecompose.core.CompanionOperation; +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.RemoteContext; +import com.android.internal.widget.remotecompose.core.WireBuffer; + +import java.util.List; + +/** + * Represents a single integer typically used for states + * or named for input into the system + */ +public class IntegerConstant implements Operation { + private int mValue = 0; + private int mId; + + IntegerConstant(int id, int value) { + mId = id; + mValue = value; + } + + @Override + public void write(WireBuffer buffer) { + COMPANION.apply(buffer, mId, mValue); + } + + @Override + public void apply(RemoteContext context) { + context.loadInteger(mId, mValue); + } + + @Override + public String deepToString(String indent) { + return toString(); + } + + @Override + public String toString() { + return "IntegerConstant[" + mId + "] = " + mValue + ""; + } + + public static final Companion COMPANION = new Companion(); + + public static class Companion implements CompanionOperation { + private Companion() { + } + + @Override + public String name() { + return "IntegerConstant"; + } + + @Override + public int id() { + return Operations.DATA_INT; + } + + /** + * Writes out the operation to the buffer + * + * @param buffer + * @param textId + * @param value + */ + public void apply(WireBuffer buffer, int textId, int value) { + buffer.start(Operations.DATA_INT); + buffer.writeInt(textId); + buffer.writeInt(value); + } + + @Override + public void read(WireBuffer buffer, List<Operation> operations) { + int id = buffer.readInt(); + + int value = buffer.readInt(); + operations.add(new IntegerConstant(id, value)); + } + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java index 73e94fa6ecf4..b2406bf69ec8 100644 --- a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java +++ b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java @@ -410,4 +410,3 @@ public class RemoteComposePlayer extends FrameLayout { } } } - diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java index ecb68bb21fb3..39a770acbf97 100644 --- a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java +++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java @@ -39,12 +39,16 @@ import com.android.internal.widget.remotecompose.core.operations.Utils; import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle; import com.android.internal.widget.remotecompose.core.operations.paint.PaintChanges; +import java.util.ArrayList; +import java.util.List; + /** * An implementation of PaintContext for the Android Canvas. * This is used to play the RemoteCompose operations on Android. */ public class AndroidPaintContext extends PaintContext { Paint mPaint = new Paint(); + List<Paint> mPaintList = new ArrayList<>(); Canvas mCanvas; Rect mTmpRect = new Rect(); // use in calculation of bounds @@ -162,6 +166,16 @@ public class AndroidPaintContext extends PaintContext { } @Override + public void savePaint() { + mPaintList.add(new Paint(mPaint)); + } + + @Override + public void restorePaint() { + mPaint = mPaintList.remove(mPaintList.size() - 1); + } + + @Override public void drawRoundRect(float left, float top, float right, @@ -335,6 +349,11 @@ public class AndroidPaintContext extends PaintContext { return null; } + /** + * This applies paint changes to the current paint + * + * @param mPaintData the list change to the paint + */ @Override public void applyPaint(PaintBundle mPaintData) { mPaintData.applyPaintChange((PaintContext) this, new PaintChanges() { diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java index dd43bd5d32a6..5a87c7083c9d 100644 --- a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java +++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java @@ -120,6 +120,11 @@ class AndroidRemoteContext extends RemoteContext { mRemoteComposeState.updateFloat(id, value); } + @Override + public void loadInteger(int id, int value) { + mRemoteComposeState.updateInteger(id, value); + } + @Override public void loadColor(int id, int color) { @@ -142,6 +147,11 @@ class AndroidRemoteContext extends RemoteContext { } @Override + public int getInteger(int id) { + return mRemoteComposeState.getInteger(id); + } + + @Override public int getColor(int id) { return mRemoteComposeState.getColor(id); } diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/FloatsToPath.java b/core/java/com/android/internal/widget/remotecompose/player/platform/FloatsToPath.java index 2d766f8da295..7a8542712cbf 100644 --- a/core/java/com/android/internal/widget/remotecompose/player/platform/FloatsToPath.java +++ b/core/java/com/android/internal/widget/remotecompose/player/platform/FloatsToPath.java @@ -58,7 +58,7 @@ public class FloatsToPath { break; case PathData.CONIC: { i += 3; - if (Build.VERSION.SDK_INT >= 34) { + if (Build.VERSION.SDK_INT >= 34) { // REMOVE IN PLATFORM path.conicTo( floatPath[i + 0], floatPath[i + 1], floatPath[i + 2], floatPath[i + 3], |