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