1 /* 2 * Copyright 2018 Google Inc. 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8 #include "SlideDir.h" 9 10 #include "SkAnimTimer.h" 11 #include "SkCanvas.h" 12 #include "SkCubicMap.h" 13 #include "SkMakeUnique.h" 14 #include "SkSGColor.h" 15 #include "SkSGDraw.h" 16 #include "SkSGGroup.h" 17 #include "SkSGPlane.h" 18 #include "SkSGRect.h" 19 #include "SkSGRenderNode.h" 20 #include "SkSGScene.h" 21 #include "SkSGText.h" 22 #include "SkSGTransform.h" 23 #include "SkTypeface.h" 24 25 #include <cmath> 26 27 namespace { 28 29 static constexpr float kAspectRatio = 1.5f; 30 static constexpr float kLabelSize = 12.0f; 31 static constexpr SkSize kPadding = { 12.0f , 24.0f }; 32 33 static constexpr float kFocusDuration = 500; 34 static constexpr SkSize kFocusInset = { 100.0f, 100.0f }; 35 static constexpr SkPoint kFocusCtrl0 = { 0.3f, 1.0f }; 36 static constexpr SkPoint kFocusCtrl1 = { 0.0f, 1.0f }; 37 static constexpr SkColor kFocusShade = 0xa0000000; 38 39 // TODO: better unfocus binding? 40 static constexpr SkUnichar kUnfocusKey = ' '; 41 42 class SlideAdapter final : public sksg::RenderNode { 43 public: 44 explicit SlideAdapter(sk_sp<Slide> slide) 45 : fSlide(std::move(slide)) { 46 SkASSERT(fSlide); 47 } 48 49 std::unique_ptr<sksg::Animator> makeForwardingAnimator() { 50 // Trivial sksg::Animator -> skottie::Animation tick adapter 51 class ForwardingAnimator final : public sksg::Animator { 52 public: 53 explicit ForwardingAnimator(sk_sp<SlideAdapter> adapter) 54 : fAdapter(std::move(adapter)) {} 55 56 protected: 57 void onTick(float t) override { 58 fAdapter->tick(SkScalarRoundToInt(t)); 59 } 60 61 private: 62 sk_sp<SlideAdapter> fAdapter; 63 }; 64 65 return skstd::make_unique<ForwardingAnimator>(sk_ref_sp(this)); 66 } 67 68 protected: 69 SkRect onRevalidate(sksg::InvalidationController* ic, const SkMatrix& ctm) override { 70 const auto isize = fSlide->getDimensions(); 71 return SkRect::MakeIWH(isize.width(), isize.height()); 72 } 73 74 void onRender(SkCanvas* canvas) const override { 75 fSlide->draw(canvas); 76 } 77 78 private: 79 void tick(SkMSec t) { 80 fSlide->animate(SkAnimTimer(0, t * 1e6, SkAnimTimer::kRunning_State)); 81 this->invalidate(); 82 } 83 84 const sk_sp<Slide> fSlide; 85 86 using INHERITED = sksg::RenderNode; 87 }; 88 89 SkMatrix SlideMatrix(const sk_sp<Slide>& slide, const SkRect& dst) { 90 const auto slideSize = slide->getDimensions(); 91 return SkMatrix::MakeRectToRect(SkRect::MakeIWH(slideSize.width(), slideSize.height()), 92 dst, 93 SkMatrix::kCenter_ScaleToFit); 94 } 95 96 } // namespace 97 98 struct SlideDir::Rec { 99 sk_sp<Slide> fSlide; 100 sk_sp<sksg::Transform> fTransform; 101 SkRect fRect; 102 }; 103 104 class SlideDir::FocusController final : public sksg::Animator { 105 public: 106 FocusController(const SlideDir* dir, const SkRect& focusRect) 107 : fDir(dir) 108 , fRect(focusRect) 109 , fTarget(nullptr) 110 , fState(State::kIdle) { 111 fMap.setPts(kFocusCtrl1, kFocusCtrl0); 112 113 fShadePaint = sksg::Color::Make(kFocusShade); 114 fShade = sksg::Draw::Make(sksg::Plane::Make(), fShadePaint); 115 } 116 117 bool hasFocus() const { return fState == State::kFocused; } 118 119 void startFocus(const Rec* target) { 120 if (fState != State::kIdle) 121 return; 122 123 fTarget = target; 124 125 // Move the shade & slide to front. 126 fDir->fRoot->removeChild(fTarget->fTransform); 127 fDir->fRoot->addChild(fShade); 128 fDir->fRoot->addChild(fTarget->fTransform); 129 130 fM0 = SlideMatrix(fTarget->fSlide, fTarget->fRect); 131 fM1 = SlideMatrix(fTarget->fSlide, fRect); 132 133 fOpacity0 = 0; 134 fOpacity1 = 1; 135 136 fTimeBase = 0; 137 fState = State::kFocusing; 138 139 // Push initial state to the scene graph. 140 this->onTick(fTimeBase); 141 } 142 143 void startUnfocus() { 144 SkASSERT(fTarget); 145 146 SkTSwap(fM0, fM1); 147 SkTSwap(fOpacity0, fOpacity1); 148 149 fTimeBase = 0; 150 fState = State::kUnfocusing; 151 } 152 153 bool onMouse(SkScalar x, SkScalar y, sk_app::Window::InputState state, uint32_t modifiers) { 154 SkASSERT(fTarget); 155 156 if (!fRect.contains(x, y)) { 157 this->startUnfocus(); 158 return true; 159 } 160 161 // Map coords to slide space. 162 const auto xform = SkMatrix::MakeRectToRect(fRect, 163 SkRect::MakeSize(fDir->fWinSize), 164 SkMatrix::kCenter_ScaleToFit); 165 const auto pt = xform.mapXY(x, y); 166 167 return fTarget->fSlide->onMouse(pt.x(), pt.y(), state, modifiers); 168 } 169 170 bool onChar(SkUnichar c) { 171 SkASSERT(fTarget); 172 173 return fTarget->fSlide->onChar(c); 174 } 175 176 protected: 177 void onTick(float t) { 178 if (!this->isAnimating()) 179 return; 180 181 if (!fTimeBase) { 182 fTimeBase = t; 183 } 184 185 const auto rel_t = (t - fTimeBase) / kFocusDuration, 186 map_t = SkTPin(fMap.computeYFromX(rel_t), 0.0f, 1.0f); 187 188 SkMatrix m; 189 for (int i = 0; i < 9; ++i) { 190 m[i] = fM0[i] + map_t * (fM1[i] - fM0[i]); 191 } 192 193 SkASSERT(fTarget); 194 fTarget->fTransform->getMatrix()->setMatrix(m); 195 196 const auto shadeOpacity = fOpacity0 + map_t * (fOpacity1 - fOpacity0); 197 fShadePaint->setOpacity(shadeOpacity); 198 199 if (rel_t < 1) 200 return; 201 202 switch (fState) { 203 case State::kFocusing: 204 fState = State::kFocused; 205 break; 206 case State::kUnfocusing: 207 fState = State::kIdle; 208 fDir->fRoot->removeChild(fShade); 209 break; 210 211 case State::kIdle: 212 case State::kFocused: 213 SkASSERT(false); 214 break; 215 } 216 } 217 218 private: 219 enum class State { 220 kIdle, 221 kFocusing, 222 kUnfocusing, 223 kFocused, 224 }; 225 226 bool isAnimating() const { return fState == State::kFocusing || fState == State::kUnfocusing; } 227 228 const SlideDir* fDir; 229 const SkRect fRect; 230 const Rec* fTarget; 231 232 SkCubicMap fMap; 233 sk_sp<sksg::RenderNode> fShade; 234 sk_sp<sksg::PaintNode> fShadePaint; 235 236 SkMatrix fM0 = SkMatrix::I(), 237 fM1 = SkMatrix::I(); 238 float fOpacity0 = 0, 239 fOpacity1 = 1, 240 fTimeBase = 0; 241 State fState = State::kIdle; 242 243 using INHERITED = sksg::Animator; 244 }; 245 246 SlideDir::SlideDir(const SkString& name, SkTArray<sk_sp<Slide>, true>&& slides, int columns) 247 : fSlides(std::move(slides)) 248 , fColumns(columns) { 249 fName = name; 250 } 251 252 static sk_sp<sksg::RenderNode> MakeLabel(const SkString& txt, 253 const SkPoint& pos, 254 const SkMatrix& dstXform) { 255 const auto size = kLabelSize / std::sqrt(dstXform.getScaleX() * dstXform.getScaleY()); 256 auto text = sksg::Text::Make(nullptr, txt); 257 text->setFlags(SkPaint::kAntiAlias_Flag); 258 text->setSize(size); 259 text->setAlign(SkPaint::kCenter_Align); 260 text->setPosition(pos + SkPoint::Make(0, size)); 261 262 return sksg::Draw::Make(std::move(text), sksg::Color::Make(SK_ColorBLACK)); 263 } 264 265 void SlideDir::load(SkScalar winWidth, SkScalar winHeight) { 266 // Build a global scene using transformed animation fragments: 267 // 268 // [Group(root)] 269 // [Transform] 270 // [Group] 271 // [AnimationWrapper] 272 // [Draw] 273 // [Text] 274 // [Color] 275 // [Transform] 276 // [Group] 277 // [AnimationWrapper] 278 // [Draw] 279 // [Text] 280 // [Color] 281 // ... 282 // 283 284 fWinSize = SkSize::Make(winWidth, winHeight); 285 const auto cellWidth = winWidth / fColumns; 286 fCellSize = SkSize::Make(cellWidth, cellWidth / kAspectRatio); 287 288 sksg::AnimatorList sceneAnimators; 289 fRoot = sksg::Group::Make(); 290 291 for (int i = 0; i < fSlides.count(); ++i) { 292 const auto& slide = fSlides[i]; 293 slide->load(winWidth, winHeight); 294 295 const auto slideSize = slide->getDimensions(); 296 const auto cell = SkRect::MakeXYWH(fCellSize.width() * (i % fColumns), 297 fCellSize.height() * (i / fColumns), 298 fCellSize.width(), 299 fCellSize.height()), 300 slideRect = cell.makeInset(kPadding.width(), kPadding.height()); 301 302 auto slideMatrix = SlideMatrix(slide, slideRect); 303 auto adapter = sk_make_sp<SlideAdapter>(slide); 304 auto slideGrp = sksg::Group::Make(); 305 slideGrp->addChild(sksg::Draw::Make(sksg::Rect::Make(SkRect::MakeIWH(slideSize.width(), 306 slideSize.height())), 307 sksg::Color::Make(0xfff0f0f0))); 308 slideGrp->addChild(adapter); 309 slideGrp->addChild(MakeLabel(slide->getName(), 310 SkPoint::Make(slideSize.width() / 2, slideSize.height()), 311 slideMatrix)); 312 auto slideTransform = sksg::Transform::Make(std::move(slideGrp), slideMatrix); 313 314 sceneAnimators.push_back(adapter->makeForwardingAnimator()); 315 316 fRoot->addChild(slideTransform); 317 fRecs.push_back({ slide, slideTransform, slideRect }); 318 } 319 320 fScene = sksg::Scene::Make(fRoot, std::move(sceneAnimators)); 321 322 const auto focusRect = SkRect::MakeSize(fWinSize).makeInset(kFocusInset.width(), 323 kFocusInset.height()); 324 fFocusController = skstd::make_unique<FocusController>(this, focusRect); 325 } 326 327 void SlideDir::unload() { 328 for (const auto& slide : fSlides) { 329 slide->unload(); 330 } 331 332 fRecs.reset(); 333 fScene.reset(); 334 fFocusController.reset(); 335 fRoot.reset(); 336 fTimeBase = 0; 337 } 338 339 SkISize SlideDir::getDimensions() const { 340 return SkSize::Make(fWinSize.width(), 341 fCellSize.height() * (1 + (fSlides.count() - 1) / fColumns)).toCeil(); 342 } 343 344 void SlideDir::draw(SkCanvas* canvas) { 345 fScene->render(canvas); 346 } 347 348 bool SlideDir::animate(const SkAnimTimer& timer) { 349 if (fTimeBase == 0) { 350 // Reset the animation time. 351 fTimeBase = timer.msec(); 352 } 353 354 const auto t = timer.msec() - fTimeBase; 355 fScene->animate(t); 356 fFocusController->tick(t); 357 358 return true; 359 } 360 361 bool SlideDir::onChar(SkUnichar c) { 362 if (fFocusController->hasFocus()) { 363 if (c == kUnfocusKey) { 364 fFocusController->startUnfocus(); 365 return true; 366 } 367 return fFocusController->onChar(c); 368 } 369 370 return false; 371 } 372 373 bool SlideDir::onMouse(SkScalar x, SkScalar y, sk_app::Window::InputState state, 374 uint32_t modifiers) { 375 if (state == sk_app::Window::kMove_InputState || modifiers) 376 return false; 377 378 if (fFocusController->hasFocus()) { 379 return fFocusController->onMouse(x, y, state, modifiers); 380 } 381 382 const auto* cell = this->findCell(x, y); 383 if (!cell) 384 return false; 385 386 static constexpr SkScalar kClickMoveTolerance = 4; 387 388 switch (state) { 389 case sk_app::Window::kDown_InputState: 390 fTrackingCell = cell; 391 fTrackingPos = SkPoint::Make(x, y); 392 break; 393 case sk_app::Window::kUp_InputState: 394 if (cell == fTrackingCell && 395 SkPoint::Distance(fTrackingPos, SkPoint::Make(x, y)) < kClickMoveTolerance) { 396 fFocusController->startFocus(cell); 397 } 398 break; 399 default: 400 break; 401 } 402 403 return false; 404 } 405 406 const SlideDir::Rec* SlideDir::findCell(float x, float y) const { 407 // TODO: use SG hit testing instead of layout info? 408 const auto size = this->getDimensions(); 409 if (x < 0 || y < 0 || x >= size.width() || y >= size.height()) { 410 return nullptr; 411 } 412 413 const int col = static_cast<int>(x / fCellSize.width()), 414 row = static_cast<int>(y / fCellSize.height()), 415 idx = row * fColumns + col; 416 417 return idx < fRecs.count() ? &fRecs[idx] : nullptr; 418 } 419