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 
     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