• 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 
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:
SlideAdapter(sk_sp<Slide> slide)44     explicit SlideAdapter(sk_sp<Slide> slide)
45         : fSlide(std::move(slide)) {
46         SkASSERT(fSlide);
47     }
48 
makeForwardingAnimator()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:
onRevalidate(sksg::InvalidationController * ic,const SkMatrix & ctm)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 
onRender(SkCanvas * canvas) const74     void onRender(SkCanvas* canvas) const override {
75         fSlide->draw(canvas);
76     }
77 
78 private:
tick(SkMSec t)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 
SlideMatrix(const sk_sp<Slide> & slide,const SkRect & dst)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:
FocusController(const SlideDir * dir,const SkRect & focusRect)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 
hasFocus() const117     bool hasFocus() const { return fState == State::kFocused; }
118 
startFocus(const Rec * target)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 
startUnfocus()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 
onMouse(SkScalar x,SkScalar y,sk_app::Window::InputState state,uint32_t modifiers)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 
onChar(SkUnichar c)170     bool onChar(SkUnichar c) {
171         SkASSERT(fTarget);
172 
173         return fTarget->fSlide->onChar(c);
174     }
175 
176 protected:
onTick(float t)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 
isAnimating() const226     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 
SlideDir(const SkString & name,SkTArray<sk_sp<Slide>,true> && slides,int columns)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 
MakeLabel(const SkString & txt,const SkPoint & pos,const SkMatrix & dstXform)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 
load(SkScalar winWidth,SkScalar winHeight)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 
unload()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 
getDimensions() const339 SkISize SlideDir::getDimensions() const {
340     return SkSize::Make(fWinSize.width(),
341                         fCellSize.height() * (1 + (fSlides.count() - 1) / fColumns)).toCeil();
342 }
343 
draw(SkCanvas * canvas)344 void SlideDir::draw(SkCanvas* canvas) {
345     fScene->render(canvas);
346 }
347 
animate(const SkAnimTimer & timer)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 
onChar(SkUnichar c)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 
onMouse(SkScalar x,SkScalar y,sk_app::Window::InputState state,uint32_t modifiers)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 
findCell(float x,float y) const406 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