• 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 "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 "modules/sksg/include/SkSGDraw.h"
14 #include "modules/sksg/include/SkSGGroup.h"
15 #include "modules/sksg/include/SkSGPaint.h"
16 #include "modules/sksg/include/SkSGPlane.h"
17 #include "modules/sksg/include/SkSGRect.h"
18 #include "modules/sksg/include/SkSGRenderNode.h"
19 #include "modules/sksg/include/SkSGScene.h"
20 #include "modules/sksg/include/SkSGText.h"
21 #include "modules/sksg/include/SkSGTransform.h"
22 #include "src/core/SkMakeUnique.h"
23 #include "tools/timer/TimeUtils.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     sk_sp<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 sk_make_sp<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 
onNodeAt(const SkPoint &) const83     const RenderNode* onNodeAt(const SkPoint&) const override { return nullptr; }
84 
85 private:
tick(SkMSec t)86     void tick(SkMSec t) {
87         fSlide->animate(t * 1e6);
88         this->invalidate();
89     }
90 
91     const sk_sp<Slide> fSlide;
92 
93     using INHERITED = sksg::RenderNode;
94 };
95 
SlideMatrix(const sk_sp<Slide> & slide,const SkRect & dst)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:
FocusController(const SlideDir * dir,const SkRect & focusRect)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 
hasFocus() const124     bool hasFocus() const { return fState == State::kFocused; }
125 
startFocus(const Rec * target)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 
startUnfocus()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 
onMouse(SkScalar x,SkScalar y,InputState state,ModifierKey modifiers)161     bool onMouse(SkScalar x, SkScalar y, InputState state, ModifierKey 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 
onChar(SkUnichar c)178     bool onChar(SkUnichar c) {
179         SkASSERT(fTarget);
180 
181         return fTarget->fSlide->onChar(c);
182     }
183 
184 protected:
onTick(float t)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 
isAnimating() const234     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 
SlideDir(const SkString & name,SkTArray<sk_sp<Slide>> && slides,int columns)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 
MakeLabel(const SkString & txt,const SkPoint & pos,const SkMatrix & dstXform)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 
load(SkScalar winWidth,SkScalar winHeight)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 
unload()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 
getDimensions() const347 SkISize SlideDir::getDimensions() const {
348     return SkSize::Make(fWinSize.width(),
349                         fCellSize.height() * (1 + (fSlides.count() - 1) / fColumns)).toCeil();
350 }
351 
draw(SkCanvas * canvas)352 void SlideDir::draw(SkCanvas* canvas) {
353     fScene->render(canvas);
354 }
355 
animate(double nanos)356 bool SlideDir::animate(double nanos) {
357     SkMSec msec = TimeUtils::NanosToMSec(nanos);
358     if (fTimeBase == 0) {
359         // Reset the animation time.
360         fTimeBase = msec;
361     }
362 
363     const auto t = msec - fTimeBase;
364     fScene->animate(t);
365     fFocusController->tick(t);
366 
367     return true;
368 }
369 
onChar(SkUnichar c)370 bool SlideDir::onChar(SkUnichar c) {
371     if (fFocusController->hasFocus()) {
372         if (c == kUnfocusKey) {
373             fFocusController->startUnfocus();
374             return true;
375         }
376         return fFocusController->onChar(c);
377     }
378 
379     return false;
380 }
381 
onMouse(SkScalar x,SkScalar y,InputState state,ModifierKey modifiers)382 bool SlideDir::onMouse(SkScalar x, SkScalar y, InputState state,
383                        ModifierKey modifiers) {
384     modifiers &= ~ModifierKey::kFirstPress;
385     if (state == InputState::kMove || skstd::Any(modifiers))
386         return false;
387 
388     if (fFocusController->hasFocus()) {
389         return fFocusController->onMouse(x, y, state, modifiers);
390     }
391 
392     const auto* cell = this->findCell(x, y);
393     if (!cell)
394         return false;
395 
396     static constexpr SkScalar kClickMoveTolerance = 4;
397 
398     switch (state) {
399     case InputState::kDown:
400         fTrackingCell = cell;
401         fTrackingPos = SkPoint::Make(x, y);
402         break;
403     case InputState::kUp:
404         if (cell == fTrackingCell &&
405             SkPoint::Distance(fTrackingPos, SkPoint::Make(x, y)) < kClickMoveTolerance) {
406             fFocusController->startFocus(cell);
407         }
408         break;
409     default:
410         break;
411     }
412 
413     return false;
414 }
415 
findCell(float x,float y) const416 const SlideDir::Rec* SlideDir::findCell(float x, float y) const {
417     // TODO: use SG hit testing instead of layout info?
418     const auto size = this->getDimensions();
419     if (x < 0 || y < 0 || x >= size.width() || y >= size.height()) {
420         return nullptr;
421     }
422 
423     const int col = static_cast<int>(x / fCellSize.width()),
424               row = static_cast<int>(y / fCellSize.height()),
425               idx = row * fColumns + col;
426 
427     return idx < fRecs.count() ? &fRecs[idx] : nullptr;
428 }
429