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