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