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