xref: /third_party/skia/tools/viewer/SlideDir.cpp (revision cb93a386)
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
28class SlideDir::Animator : public SkRefCnt {
29public:
30    Animator(const Animator&) = delete;
31    Animator& operator=(const Animator&) = delete;
32
33    void tick(float t) { this->onTick(t); }
34
35protected:
36    Animator() = default;
37
38    virtual void onTick(float t) = 0;
39};
40
41namespace {
42
43static constexpr float  kAspectRatio   = 1.5f;
44static constexpr float  kLabelSize     = 12.0f;
45static constexpr SkSize kPadding       = { 12.0f , 24.0f };
46
47static constexpr float   kFocusDuration = 500;
48static constexpr SkSize  kFocusInset    = { 100.0f, 100.0f };
49static constexpr SkPoint kFocusCtrl0    = {   0.3f,   1.0f };
50static constexpr SkPoint kFocusCtrl1    = {   0.0f,   1.0f };
51static constexpr SkColor kFocusShade    = 0xa0000000;
52
53// TODO: better unfocus binding?
54static constexpr SkUnichar kUnfocusKey = ' ';
55
56class SlideAdapter final : public sksg::RenderNode {
57public:
58    explicit SlideAdapter(sk_sp<Slide> slide)
59        : fSlide(std::move(slide)) {
60        SkASSERT(fSlide);
61    }
62
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
82protected:
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
88    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
96    const RenderNode* onNodeAt(const SkPoint&) const override { return nullptr; }
97
98private:
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
109SkMatrix 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
117struct 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
124class SlideDir::FocusController final : public Animator {
125public:
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
136    bool hasFocus() const { return fState == State::kFocused; }
137
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
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
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
189    bool onChar(SkUnichar c) {
190        SkASSERT(fTarget);
191
192        return fTarget->fSlide->onChar(c);
193    }
194
195protected:
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
237private:
238    enum class State {
239        kIdle,
240        kFocusing,
241        kUnfocusing,
242        kFocused,
243    };
244
245    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
265SlideDir::SlideDir(const SkString& name, SkTArray<sk_sp<Slide>>&& slides, int columns)
266    : fSlides(std::move(slides))
267    , fColumns(columns) {
268    fName = name;
269}
270
271static 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
284void 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
345void 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
357SkISize SlideDir::getDimensions() const {
358    return SkSize::Make(fWinSize.width(),
359                        fCellSize.height() * (1 + (fSlides.count() - 1) / fColumns)).toCeil();
360}
361
362void SlideDir::draw(SkCanvas* canvas) {
363    fScene->render(canvas);
364}
365
366bool 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
382bool 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
394bool 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
428const 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