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