1 /*
2  * Copyright (C) 2021 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 
17 #include <SkBitmap.h>
18 #include <SkBlendMode.h>
19 #include <SkCanvas.h>
20 #include <SkColor.h>
21 #include <SkFont.h>
22 #include <SkFontTypes.h>
23 #include <SkPaint.h>
24 #include <SkPoint.h>
25 #include <SkRefCnt.h>
26 #include <SkRRect.h>
27 #include <cstdio>
28 #include "TestSceneBase.h"
29 #include "hwui/Paint.h"
30 #include "tests/common/TestUtils.h"
31 
32 class StretchyListViewAnimation;
33 class StretchyListViewHolePunch;
34 class StretchyUniformListView;
35 class StretchyUniformListViewHolePunch;
36 class StretchyUniformLayerListView;
37 class StretchyUniformLayerListViewHolePunch;
38 
39 static TestScene::Registrar _StretchyListViewAnimation(TestScene::Info{
40         "stretchylistview",
41         "A mock ListView of scrolling content that's stretching. Doesn't re-bind/re-record views "
42         "as they are recycled, so won't upload much content (either glyphs, or bitmaps).",
43         TestScene::simpleCreateScene<StretchyListViewAnimation>});
44 
45 static TestScene::Registrar _StretchyListViewHolePunch(TestScene::Info{
46         "stretchylistview_holepunch",
47         "A mock ListView of scrolling content that's stretching. Includes a hole punch",
48         TestScene::simpleCreateScene<StretchyListViewHolePunch>});
49 
50 static TestScene::Registrar _StretchyUniformListView(TestScene::Info{
51         "stretchylistview_uniform",
52         "A mock ListView of scrolling content that's stretching using a uniform stretch effect.",
53         TestScene::simpleCreateScene<StretchyUniformListView>});
54 
55 static TestScene::Registrar _StretchyUniformListViewHolePunch(TestScene::Info{
56         "stretchylistview_uniform_holepunch",
57         "A mock ListView of scrolling content that's stretching using a uniform stretch effect. "
58         "Includes a hole punch",
59         TestScene::simpleCreateScene<StretchyUniformListViewHolePunch>});
60 
61 static TestScene::Registrar _StretchyUniformLayerListView(TestScene::Info{
62         "stretchylistview_uniform_layer",
63         "A mock ListView of scrolling content that's stretching using a uniform stretch effect. "
64         "Uses a layer",
65         TestScene::simpleCreateScene<StretchyUniformLayerListView>});
66 
67 static TestScene::Registrar _StretchyUniformLayerListViewHolePunch(TestScene::Info{
68         "stretchylistview_uniform_layer_holepunch",
69         "A mock ListView of scrolling content that's stretching using a uniform stretch effect. "
70         "Uses a layer & includes a hole punch",
71         TestScene::simpleCreateScene<StretchyUniformLayerListViewHolePunch>});
72 
73 class StretchyListViewAnimation : public TestScene {
74 protected:
stretchBehavior()75     virtual StretchEffectBehavior stretchBehavior() { return StretchEffectBehavior::Shader; }
haveHolePunch()76     virtual bool haveHolePunch() { return false; }
forceLayer()77     virtual bool forceLayer() { return false; }
78 
79 private:
80     int mItemHeight;
81     int mItemSpacing;
82     int mItemWidth;
83     int mItemLeft;
84     sp<RenderNode> mListView;
85     std::vector<sp<RenderNode> > mListItems;
86 
createRandomCharIcon(int cardHeight)87     sk_sp<Bitmap> createRandomCharIcon(int cardHeight) {
88         SkBitmap skBitmap;
89         int size = cardHeight - (dp(10) * 2);
90         sk_sp<Bitmap> bitmap(TestUtils::createBitmap(size, size, &skBitmap));
91         SkCanvas canvas(skBitmap);
92         canvas.clear(0);
93 
94         SkPaint paint;
95         paint.setAntiAlias(true);
96         SkColor randomColor = BrightColors[rand() % BrightColorsCount];
97         paint.setColor(randomColor);
98         canvas.drawCircle(size / 2, size / 2, size / 2, paint);
99 
100         bool bgDark =
101                 SkColorGetR(randomColor) + SkColorGetG(randomColor) + SkColorGetB(randomColor) <
102                 128 * 3;
103         paint.setColor(bgDark ? Color::White : Color::Grey_700);
104 
105         SkFont font = TestUtils::defaultFont();
106         font.setSize(size / 2);
107         char charToShow = 'A' + (rand() % 26);
108         const SkPoint pos = {SkIntToScalar(size / 2),
109                              /*approximate centering*/ SkFloatToScalar(size * 0.7f)};
110         canvas.drawSimpleText(&charToShow, 1, SkTextEncoding::kUTF8, pos.fX, pos.fY, font, paint);
111         return bitmap;
112     }
113 
createBoxBitmap(bool filled)114     static sk_sp<Bitmap> createBoxBitmap(bool filled) {
115         int size = dp(20);
116         int stroke = dp(2);
117         SkBitmap skBitmap;
118         auto bitmap = TestUtils::createBitmap(size, size, &skBitmap);
119         SkCanvas canvas(skBitmap);
120         canvas.clear(Color::Transparent);
121 
122         SkPaint paint;
123         paint.setAntiAlias(true);
124         paint.setColor(filled ? Color::Yellow_500 : Color::Grey_700);
125         paint.setStyle(filled ? SkPaint::kStrokeAndFill_Style : SkPaint::kStroke_Style);
126         paint.setStrokeWidth(stroke);
127         canvas.drawRect(SkRect::MakeLTRB(stroke, stroke, size - stroke, size - stroke), paint);
128         return bitmap;
129     }
130 
createListItem(RenderProperties & props,Canvas & canvas,int cardId,int itemWidth,int itemHeight)131     void createListItem(RenderProperties& props, Canvas& canvas, int cardId, int itemWidth,
132                         int itemHeight) {
133         static sk_sp<Bitmap> filledBox(createBoxBitmap(true));
134         static sk_sp<Bitmap> strokedBox(createBoxBitmap(false));
135         const bool addHolePunch = cardId == 2 && haveHolePunch();
136         // TODO: switch to using round rect clipping, once merging correctly handles that
137         Paint roundRectPaint;
138         roundRectPaint.setAntiAlias(true);
139         roundRectPaint.setColor(Color::White);
140         if (addHolePunch) {
141             // Punch a hole but then cover it up, we don't want to actually see it
142             canvas.punchHole(SkRRect::MakeRect(SkRect::MakeWH(itemWidth, itemHeight)), 1.f);
143         }
144         canvas.drawRoundRect(0, 0, itemWidth, itemHeight, dp(6), dp(6), roundRectPaint);
145 
146         Paint textPaint;
147         textPaint.setColor(rand() % 2 ? Color::Black : Color::Grey_500);
148         textPaint.getSkFont().setSize(dp(20));
149         textPaint.setAntiAlias(true);
150         char buf[256];
151         snprintf(buf, sizeof(buf), "This card is #%d", cardId);
152         TestUtils::drawUtf8ToCanvas(&canvas, buf, textPaint, itemHeight, dp(25));
153         textPaint.getSkFont().setSize(dp(15));
154         if (addHolePunch) {
155             TestUtils::drawUtf8ToCanvas(&canvas, "I have a hole punch", textPaint, itemHeight,
156                                         dp(45));
157         } else {
158             TestUtils::drawUtf8ToCanvas(&canvas, "This is some more text on the card", textPaint,
159                                         itemHeight, dp(45));
160         }
161 
162         auto randomIcon = createRandomCharIcon(itemHeight);
163         canvas.drawBitmap(*randomIcon, dp(10), dp(10), nullptr);
164 
165         auto box = rand() % 2 ? filledBox : strokedBox;
166         canvas.drawBitmap(*box, itemWidth - dp(10) - box->width(), dp(10), nullptr);
167     }
168 
createContent(int width,int height,Canvas & canvas)169     void createContent(int width, int height, Canvas& canvas) override {
170         srand(0);
171         mItemHeight = dp(60);
172         mItemSpacing = dp(16);
173         mItemWidth = std::min((height - mItemSpacing * 2), (int)dp(300));
174         mItemLeft = (width - mItemWidth) / 2;
175         int heightWithSpacing = mItemHeight + mItemSpacing;
176         for (int y = 0; y < height + (heightWithSpacing - 1); y += heightWithSpacing) {
177             int id = mListItems.size();
178             auto node = TestUtils::createNode(mItemLeft, y, mItemLeft + mItemWidth, y + mItemHeight,
179                                               [this, id](RenderProperties& props, Canvas& canvas) {
180                                                   createListItem(props, canvas, id, mItemWidth,
181                                                                  mItemHeight);
182                                               });
183             mListItems.push_back(node);
184         }
185         mListView = TestUtils::createNode(0, 0, width, height,
186                                           [this](RenderProperties& props, Canvas& canvas) {
187                                               for (size_t ci = 0; ci < mListItems.size(); ci++) {
188                                                   canvas.drawRenderNode(mListItems[ci].get());
189                                               }
190                                           });
191 
192         canvas.drawColor(Color::Grey_500, SkBlendMode::kSrcOver);
193         canvas.drawRenderNode(mListView.get());
194     }
195 
doFrame(int frameNr)196     void doFrame(int frameNr) override {
197         if (frameNr == 0) {
198             Properties::setStretchEffectBehavior(stretchBehavior());
199             if (forceLayer()) {
200                 mListView->mutateStagingProperties().mutateLayerProperties().setType(
201                         LayerType::RenderLayer);
202             }
203         }
204         auto& props = mListView->mutateStagingProperties();
205         auto& stretch = props.mutateLayerProperties().mutableStretchEffect();
206         stretch.setEmpty();
207         frameNr = frameNr % 150;
208         // Animate from 0f to .1f
209         const float sY = (frameNr > 75 ? 150 - frameNr : frameNr) / 1500.f;
210         stretch.mergeWith({{.fX = 0, .fY = sY},
211                            static_cast<float>(props.getWidth()),
212                            static_cast<float>(props.getHeight())});
213         mListView->setPropertyFieldsDirty(RenderNode::GENERIC);
214     }
215 };
216 
217 class StretchyListViewHolePunch : public StretchyListViewAnimation {
haveHolePunch()218     bool haveHolePunch() override { return true; }
219 };
220 
221 class StretchyUniformListView : public StretchyListViewAnimation {
stretchBehavior()222     StretchEffectBehavior stretchBehavior() override { return StretchEffectBehavior::UniformScale; }
223 };
224 
225 class StretchyUniformListViewHolePunch : public StretchyListViewAnimation {
stretchBehavior()226     StretchEffectBehavior stretchBehavior() override { return StretchEffectBehavior::UniformScale; }
haveHolePunch()227     bool haveHolePunch() override { return true; }
228 };
229 
230 class StretchyUniformLayerListView : public StretchyListViewAnimation {
stretchBehavior()231     StretchEffectBehavior stretchBehavior() override { return StretchEffectBehavior::UniformScale; }
forceLayer()232     bool forceLayer() override { return true; }
233 };
234 
235 class StretchyUniformLayerListViewHolePunch : public StretchyListViewAnimation {
stretchBehavior()236     StretchEffectBehavior stretchBehavior() override { return StretchEffectBehavior::UniformScale; }
haveHolePunch()237     bool haveHolePunch() override { return true; }
forceLayer()238     bool forceLayer() override { return true; }
239 };
240