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