• 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 "include/private/SkTPin.h"
14 #include "modules/sksg/include/SkSGDraw.h"
15 #include "modules/sksg/include/SkSGGroup.h"
16 #include "modules/sksg/include/SkSGPaint.h"
17 #include "modules/sksg/include/SkSGPlane.h"
18 #include "modules/sksg/include/SkSGRect.h"
19 #include "modules/sksg/include/SkSGRenderNode.h"
20 #include "modules/sksg/include/SkSGScene.h"
21 #include "modules/sksg/include/SkSGText.h"
22 #include "modules/sksg/include/SkSGTransform.h"
23 #include "tools/timer/TimeUtils.h"
24 
25 #include <cmath>
26 #include <utility>
27 
28 class SlideDir::Animator : public SkRefCnt {
29 public:
30     Animator(const Animator&) = delete;
31     Animator& operator=(const Animator&) = delete;
32 
tick(float t)33     void tick(float t) { this->onTick(t); }
34 
35 protected:
36     Animator() = default;
37 
38     virtual void onTick(float t) = 0;
39 };
40 
41 namespace {
42 
43 static constexpr float  kAspectRatio   = 1.5f;
44 static constexpr float  kLabelSize     = 12.0f;
45 static constexpr SkSize kPadding       = { 12.0f , 24.0f };
46 
47 static constexpr float   kFocusDuration = 500;
48 static constexpr SkSize  kFocusInset    = { 100.0f, 100.0f };
49 static constexpr SkPoint kFocusCtrl0    = {   0.3f,   1.0f };
50 static constexpr SkPoint kFocusCtrl1    = {   0.0f,   1.0f };
51 static constexpr SkColor kFocusShade    = 0xa0000000;
52 
53 // TODO: better unfocus binding?
54 static constexpr SkUnichar kUnfocusKey = ' ';
55 
56 class SlideAdapter final : public sksg::RenderNode {
57 public:
SlideAdapter(sk_sp<Slide> slide)58     explicit SlideAdapter(sk_sp<Slide> slide)
59         : fSlide(std::move(slide)) {
60         SkASSERT(fSlide);
61     }
62 
makeForwardingAnimator()63     sk_sp<SlideDir::Animator> makeForwardingAnimator() {
64         // Trivial sksg::Animator -> skottie::Animation tick adapter
65         class ForwardingAnimator final : public SlideDir::Animator {
66         public:
67             explicit ForwardingAnimator(sk_sp<SlideAdapter> adapter)
68                 : fAdapter(std::move(adapter)) {}
69 
70         protected:
71             void onTick(float t) override {
72                 fAdapter->tick(SkScalarRoundToInt(t));
73             }
74 
75         private:
76             sk_sp<SlideAdapter> fAdapter;
77         };
78 
79         return sk_make_sp<ForwardingAnimator>(sk_ref_sp(this));
80     }
81 
82 protected:
onRevalidate(sksg::InvalidationController * ic,const SkMatrix & ctm)83     SkRect onRevalidate(sksg::InvalidationController* ic, const SkMatrix& ctm) override {
84         const auto isize = fSlide->getDimensions();
85         return SkRect::MakeIWH(isize.width(), isize.height());
86     }
87 
onRender(SkCanvas * canvas,const RenderContext * ctx) const88     void onRender(SkCanvas* canvas, const RenderContext* ctx) const override {
89         SkAutoCanvasRestore acr(canvas, true);
90         canvas->clipRect(SkRect::Make(fSlide->getDimensions()), true);
91 
92         // TODO: commit the context?
93         fSlide->draw(canvas);
94     }
95 
onNodeAt(const SkPoint &) const96     const RenderNode* onNodeAt(const SkPoint&) const override { return nullptr; }
97 
98 private:
tick(SkMSec t)99     void tick(SkMSec t) {
100         fSlide->animate(t * 1e6);
101         this->invalidate();
102     }
103 
104     const sk_sp<Slide> fSlide;
105 
106     using INHERITED = sksg::RenderNode;
107 };
108 
SlideMatrix(const sk_sp<Slide> & slide,const SkRect & dst)109 SkMatrix SlideMatrix(const sk_sp<Slide>& slide, const SkRect& dst) {
110     const auto slideSize = slide->getDimensions();
111     return SkMatrix::RectToRect(SkRect::MakeIWH(slideSize.width(), slideSize.height()), dst,
112                                 SkMatrix::kCenter_ScaleToFit);
113 }
114 
115 } // namespace
116 
117 struct SlideDir::Rec {
118     sk_sp<Slide>                  fSlide;
119     sk_sp<sksg::RenderNode>       fSlideRoot;
120     sk_sp<sksg::Matrix<SkMatrix>> fMatrix;
121     SkRect                        fRect;
122 };
123 
124 class SlideDir::FocusController final : public Animator {
125 public:
FocusController(const SlideDir * dir,const SkRect & focusRect)126     FocusController(const SlideDir* dir, const SkRect& focusRect)
127         : fDir(dir)
128         , fRect(focusRect)
129         , fTarget(nullptr)
130         , fMap(kFocusCtrl1, kFocusCtrl0)
131         , fState(State::kIdle) {
132         fShadePaint = sksg::Color::Make(kFocusShade);
133         fShade = sksg::Draw::Make(sksg::Plane::Make(), fShadePaint);
134     }
135 
hasFocus() const136     bool hasFocus() const { return fState == State::kFocused; }
137 
startFocus(const Rec * target)138     void startFocus(const Rec* target) {
139         if (fState != State::kIdle)
140             return;
141 
142         fTarget = target;
143 
144         // Move the shade & slide to front.
145         fDir->fRoot->removeChild(fTarget->fSlideRoot);
146         fDir->fRoot->addChild(fShade);
147         fDir->fRoot->addChild(fTarget->fSlideRoot);
148 
149         fM0 = SlideMatrix(fTarget->fSlide, fTarget->fRect);
150         fM1 = SlideMatrix(fTarget->fSlide, fRect);
151 
152         fOpacity0 = 0;
153         fOpacity1 = 1;
154 
155         fTimeBase = 0;
156         fState = State::kFocusing;
157 
158         // Push initial state to the scene graph.
159         this->onTick(fTimeBase);
160     }
161 
startUnfocus()162     void startUnfocus() {
163         SkASSERT(fTarget);
164 
165         using std::swap;
166         swap(fM0, fM1);
167         swap(fOpacity0, fOpacity1);
168 
169         fTimeBase = 0;
170         fState = State::kUnfocusing;
171     }
172 
onMouse(SkScalar x,SkScalar y,skui::InputState state,skui::ModifierKey modifiers)173     bool onMouse(SkScalar x, SkScalar y, skui::InputState state, skui::ModifierKey modifiers) {
174         SkASSERT(fTarget);
175 
176         if (!fRect.contains(x, y)) {
177             this->startUnfocus();
178             return true;
179         }
180 
181         // Map coords to slide space.
182         const auto xform = SkMatrix::RectToRect(fRect, SkRect::MakeSize(fDir->fWinSize),
183                                                 SkMatrix::kCenter_ScaleToFit);
184         const auto pt = xform.mapXY(x, y);
185 
186         return fTarget->fSlide->onMouse(pt.x(), pt.y(), state, modifiers);
187     }
188 
onChar(SkUnichar c)189     bool onChar(SkUnichar c) {
190         SkASSERT(fTarget);
191 
192         return fTarget->fSlide->onChar(c);
193     }
194 
195 protected:
onTick(float t)196     void onTick(float t) override {
197         if (!this->isAnimating())
198             return;
199 
200         if (!fTimeBase) {
201             fTimeBase = t;
202         }
203 
204         const auto rel_t = (t - fTimeBase) / kFocusDuration,
205                    map_t = SkTPin(fMap.computeYFromX(rel_t), 0.0f, 1.0f);
206 
207         SkMatrix m;
208         for (int i = 0; i < 9; ++i) {
209             m[i] = fM0[i] + map_t * (fM1[i] - fM0[i]);
210         }
211 
212         SkASSERT(fTarget);
213         fTarget->fMatrix->setMatrix(m);
214 
215         const auto shadeOpacity = fOpacity0 + map_t * (fOpacity1 - fOpacity0);
216         fShadePaint->setOpacity(shadeOpacity);
217 
218         if (rel_t < 1)
219             return;
220 
221         switch (fState) {
222         case State::kFocusing:
223             fState = State::kFocused;
224             break;
225         case State::kUnfocusing:
226             fState  = State::kIdle;
227             fDir->fRoot->removeChild(fShade);
228             break;
229 
230         case State::kIdle:
231         case State::kFocused:
232             SkASSERT(false);
233             break;
234         }
235     }
236 
237 private:
238     enum class State {
239         kIdle,
240         kFocusing,
241         kUnfocusing,
242         kFocused,
243     };
244 
isAnimating() const245     bool isAnimating() const { return fState == State::kFocusing || fState == State::kUnfocusing; }
246 
247     const SlideDir*         fDir;
248     const SkRect            fRect;
249     const Rec*              fTarget;
250 
251     SkCubicMap              fMap;
252     sk_sp<sksg::RenderNode> fShade;
253     sk_sp<sksg::PaintNode>  fShadePaint;
254 
255     SkMatrix        fM0       = SkMatrix::I(),
256                     fM1       = SkMatrix::I();
257     float           fOpacity0 = 0,
258                     fOpacity1 = 1,
259                     fTimeBase = 0;
260     State           fState    = State::kIdle;
261 
262     using INHERITED = Animator;
263 };
264 
SlideDir(const SkString & name,SkTArray<sk_sp<Slide>> && slides,int columns)265 SlideDir::SlideDir(const SkString& name, SkTArray<sk_sp<Slide>>&& slides, int columns)
266     : fSlides(std::move(slides))
267     , fColumns(columns) {
268     fName = name;
269 }
270 
MakeLabel(const SkString & txt,const SkPoint & pos,const SkMatrix & dstXform)271 static sk_sp<sksg::RenderNode> MakeLabel(const SkString& txt,
272                                          const SkPoint& pos,
273                                          const SkMatrix& dstXform) {
274     const auto size = kLabelSize / std::sqrt(dstXform.getScaleX() * dstXform.getScaleY());
275     auto text = sksg::Text::Make(nullptr, txt);
276     text->setEdging(SkFont::Edging::kAntiAlias);
277     text->setSize(size);
278     text->setAlign(SkTextUtils::kCenter_Align);
279     text->setPosition(pos + SkPoint::Make(0, size));
280 
281     return sksg::Draw::Make(std::move(text), sksg::Color::Make(SK_ColorBLACK));
282 }
283 
load(SkScalar winWidth,SkScalar winHeight)284 void SlideDir::load(SkScalar winWidth, SkScalar winHeight) {
285     // Build a global scene using transformed animation fragments:
286     //
287     // [Group(root)]
288     //     [Transform]
289     //         [Group]
290     //             [AnimationWrapper]
291     //             [Draw]
292     //                 [Text]
293     //                 [Color]
294     //     [Transform]
295     //         [Group]
296     //             [AnimationWrapper]
297     //             [Draw]
298     //                 [Text]
299     //                 [Color]
300     //     ...
301     //
302 
303     fWinSize = SkSize::Make(winWidth, winHeight);
304     const auto  cellWidth =  winWidth / fColumns;
305     fCellSize = SkSize::Make(cellWidth, cellWidth / kAspectRatio);
306 
307     fRoot = sksg::Group::Make();
308 
309     for (int i = 0; i < fSlides.count(); ++i) {
310         const auto& slide     = fSlides[i];
311         slide->load(winWidth, winHeight);
312 
313         const auto  slideSize = slide->getDimensions();
314         const auto  cell      = SkRect::MakeXYWH(fCellSize.width()  * (i % fColumns),
315                                                  fCellSize.height() * (i / fColumns),
316                                                  fCellSize.width(),
317                                                  fCellSize.height()),
318                     slideRect = cell.makeInset(kPadding.width(), kPadding.height());
319 
320         auto slideMatrix = sksg::Matrix<SkMatrix>::Make(SlideMatrix(slide, slideRect));
321         auto adapter     = sk_make_sp<SlideAdapter>(slide);
322         auto slideGrp    = sksg::Group::Make();
323         slideGrp->addChild(sksg::Draw::Make(sksg::Rect::Make(SkRect::MakeIWH(slideSize.width(),
324                                                                              slideSize.height())),
325                                             sksg::Color::Make(0xfff0f0f0)));
326         slideGrp->addChild(adapter);
327         slideGrp->addChild(MakeLabel(slide->getName(),
328                                      SkPoint::Make(slideSize.width() / 2, slideSize.height()),
329                                      slideMatrix->getMatrix()));
330         auto slideRoot = sksg::TransformEffect::Make(std::move(slideGrp), slideMatrix);
331 
332         fSceneAnimators.push_back(adapter->makeForwardingAnimator());
333 
334         fRoot->addChild(slideRoot);
335         fRecs.push_back({ slide, slideRoot, slideMatrix, slideRect });
336     }
337 
338     fScene = sksg::Scene::Make(fRoot);
339 
340     const auto focusRect = SkRect::MakeSize(fWinSize).makeInset(kFocusInset.width(),
341                                                                 kFocusInset.height());
342     fFocusController = std::make_unique<FocusController>(this, focusRect);
343 }
344 
unload()345 void SlideDir::unload() {
346     for (const auto& slide : fSlides) {
347         slide->unload();
348     }
349 
350     fRecs.reset();
351     fScene.reset();
352     fFocusController.reset();
353     fRoot.reset();
354     fTimeBase = 0;
355 }
356 
getDimensions() const357 SkISize SlideDir::getDimensions() const {
358     return SkSize::Make(fWinSize.width(),
359                         fCellSize.height() * (1 + (fSlides.count() - 1) / fColumns)).toCeil();
360 }
361 
draw(SkCanvas * canvas)362 void SlideDir::draw(SkCanvas* canvas) {
363     fScene->render(canvas);
364 }
365 
animate(double nanos)366 bool SlideDir::animate(double nanos) {
367     SkMSec msec = TimeUtils::NanosToMSec(nanos);
368     if (fTimeBase == 0) {
369         // Reset the animation time.
370         fTimeBase = msec;
371     }
372 
373     const auto t = msec - fTimeBase;
374     for (const auto& anim : fSceneAnimators) {
375         anim->tick(t);
376     }
377     fFocusController->tick(t);
378 
379     return true;
380 }
381 
onChar(SkUnichar c)382 bool SlideDir::onChar(SkUnichar c) {
383     if (fFocusController->hasFocus()) {
384         if (c == kUnfocusKey) {
385             fFocusController->startUnfocus();
386             return true;
387         }
388         return fFocusController->onChar(c);
389     }
390 
391     return false;
392 }
393 
onMouse(SkScalar x,SkScalar y,skui::InputState state,skui::ModifierKey modifiers)394 bool SlideDir::onMouse(SkScalar x, SkScalar y, skui::InputState state,
395                        skui::ModifierKey modifiers) {
396     modifiers &= ~skui::ModifierKey::kFirstPress;
397     if (state == skui::InputState::kMove || sknonstd::Any(modifiers))
398         return false;
399 
400     if (fFocusController->hasFocus()) {
401         return fFocusController->onMouse(x, y, state, modifiers);
402     }
403 
404     const auto* cell = this->findCell(x, y);
405     if (!cell)
406         return false;
407 
408     static constexpr SkScalar kClickMoveTolerance = 4;
409 
410     switch (state) {
411     case skui::InputState::kDown:
412         fTrackingCell = cell;
413         fTrackingPos = SkPoint::Make(x, y);
414         break;
415     case skui::InputState::kUp:
416         if (cell == fTrackingCell &&
417             SkPoint::Distance(fTrackingPos, SkPoint::Make(x, y)) < kClickMoveTolerance) {
418             fFocusController->startFocus(cell);
419         }
420         break;
421     default:
422         break;
423     }
424 
425     return false;
426 }
427 
findCell(float x,float y) const428 const SlideDir::Rec* SlideDir::findCell(float x, float y) const {
429     // TODO: use SG hit testing instead of layout info?
430     const auto size = this->getDimensions();
431     if (x < 0 || y < 0 || x >= size.width() || y >= size.height()) {
432         return nullptr;
433     }
434 
435     const int col = static_cast<int>(x / fCellSize.width()),
436               row = static_cast<int>(y / fCellSize.height()),
437               idx = row * fColumns + col;
438 
439     return idx < fRecs.count() ? &fRecs[idx] : nullptr;
440 }
441