Home | History | Annotate | Download | only in viewer
      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