blob: 23e4bb1f12286b1bb93e1db73609461241dd1c4a [file] [log] [blame]
Florin Malita76a076b2018-02-15 18:40:48 -05001/*
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
Mike Kleinc0bd9f92019-04-23 12:05:21 -05008#include "tools/viewer/SlideDir.h"
Florin Malita76a076b2018-02-15 18:40:48 -05009
Mike Kleinc0bd9f92019-04-23 12:05:21 -050010#include "include/core/SkCanvas.h"
11#include "include/core/SkCubicMap.h"
12#include "include/core/SkTypeface.h"
Mike Klein8aa0edf2020-10-16 11:04:18 -050013#include "include/private/SkTPin.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050014#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"
Hal Canary41248072019-07-11 16:32:53 -040023#include "tools/timer/TimeUtils.h"
Florin Malita76a076b2018-02-15 18:40:48 -050024
Florin Malita65fce9e2018-02-19 13:25:18 -050025#include <cmath>
Ben Wagnerf08d1d02018-06-18 15:11:00 -040026#include <utility>
Florin Malita65fce9e2018-02-19 13:25:18 -050027
Florin Malitabb7d95f2020-03-26 15:58:56 -040028class SlideDir::Animator : public SkRefCnt {
29public:
Florin Malitabb7d95f2020-03-26 15:58:56 -040030 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
Florin Malita76a076b2018-02-15 18:40:48 -050041namespace {
42
Florin Malita65fce9e2018-02-19 13:25:18 -050043static constexpr float kAspectRatio = 1.5f;
44static constexpr float kLabelSize = 12.0f;
45static constexpr SkSize kPadding = { 12.0f , 24.0f };
Florin Malita836f8222018-02-20 10:31:01 -050046
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;
Florin Malita65fce9e2018-02-19 13:25:18 -050052
53// TODO: better unfocus binding?
54static constexpr SkUnichar kUnfocusKey = ' ';
Florin Malita76a076b2018-02-15 18:40:48 -050055
56class SlideAdapter final : public sksg::RenderNode {
57public:
58 explicit SlideAdapter(sk_sp<Slide> slide)
59 : fSlide(std::move(slide)) {
60 SkASSERT(fSlide);
61 }
62
Florin Malitabb7d95f2020-03-26 15:58:56 -040063 sk_sp<SlideDir::Animator> makeForwardingAnimator() {
Florin Malita76a076b2018-02-15 18:40:48 -050064 // Trivial sksg::Animator -> skottie::Animation tick adapter
Florin Malitabb7d95f2020-03-26 15:58:56 -040065 class ForwardingAnimator final : public SlideDir::Animator {
Florin Malita76a076b2018-02-15 18:40:48 -050066 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
Florin Malita5f240182019-07-23 17:28:53 -040079 return sk_make_sp<ForwardingAnimator>(sk_ref_sp(this));
Florin Malita76a076b2018-02-15 18:40:48 -050080 }
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
Florin Malitac0132ff2018-08-09 07:40:01 -040088 void onRender(SkCanvas* canvas, const RenderContext* ctx) const override {
Florin Malita6127c4c2018-04-05 15:15:59 -040089 SkAutoCanvasRestore acr(canvas, true);
90 canvas->clipRect(SkRect::Make(fSlide->getDimensions()), true);
Florin Malitac0132ff2018-08-09 07:40:01 -040091
92 // TODO: commit the context?
Florin Malita76a076b2018-02-15 18:40:48 -050093 fSlide->draw(canvas);
94 }
95
Florin Malitaeb46bd82019-02-12 09:33:21 -050096 const RenderNode* onNodeAt(const SkPoint&) const override { return nullptr; }
97
Florin Malita76a076b2018-02-15 18:40:48 -050098private:
99 void tick(SkMSec t) {
Hal Canary41248072019-07-11 16:32:53 -0400100 fSlide->animate(t * 1e6);
Florin Malita76a076b2018-02-15 18:40:48 -0500101 this->invalidate();
102 }
103
104 const sk_sp<Slide> fSlide;
105
106 using INHERITED = sksg::RenderNode;
107};
108
Florin Malita65fce9e2018-02-19 13:25:18 -0500109SkMatrix SlideMatrix(const sk_sp<Slide>& slide, const SkRect& dst) {
110 const auto slideSize = slide->getDimensions();
Mike Reed2ac6ce82021-01-15 12:26:22 -0500111 return SkMatrix::RectToRect(SkRect::MakeIWH(slideSize.width(), slideSize.height()), dst,
112 SkMatrix::kCenter_ScaleToFit);
Florin Malita65fce9e2018-02-19 13:25:18 -0500113}
114
Florin Malita76a076b2018-02-15 18:40:48 -0500115} // namespace
116
117struct SlideDir::Rec {
Florin Malita760a0522019-01-10 15:24:15 -0500118 sk_sp<Slide> fSlide;
119 sk_sp<sksg::RenderNode> fSlideRoot;
120 sk_sp<sksg::Matrix<SkMatrix>> fMatrix;
121 SkRect fRect;
Florin Malita65fce9e2018-02-19 13:25:18 -0500122};
123
Florin Malitabb7d95f2020-03-26 15:58:56 -0400124class SlideDir::FocusController final : public Animator {
Florin Malita65fce9e2018-02-19 13:25:18 -0500125public:
126 FocusController(const SlideDir* dir, const SkRect& focusRect)
127 : fDir(dir)
128 , fRect(focusRect)
129 , fTarget(nullptr)
Florin Malita34336e32019-03-06 16:34:21 -0500130 , fMap(kFocusCtrl1, kFocusCtrl0)
Florin Malita836f8222018-02-20 10:31:01 -0500131 , fState(State::kIdle) {
Florin Malita836f8222018-02-20 10:31:01 -0500132 fShadePaint = sksg::Color::Make(kFocusShade);
Florin Malita97b3d2b2018-02-20 11:26:15 -0500133 fShade = sksg::Draw::Make(sksg::Plane::Make(), fShadePaint);
Florin Malita836f8222018-02-20 10:31:01 -0500134 }
Florin Malita65fce9e2018-02-19 13:25:18 -0500135
136 bool hasFocus() const { return fState == State::kFocused; }
137
138 void startFocus(const Rec* target) {
Florin Malita836f8222018-02-20 10:31:01 -0500139 if (fState != State::kIdle)
140 return;
141
Florin Malita65fce9e2018-02-19 13:25:18 -0500142 fTarget = target;
Florin Malita836f8222018-02-20 10:31:01 -0500143
144 // Move the shade & slide to front.
Florin Malita919e2092019-01-09 15:37:57 -0500145 fDir->fRoot->removeChild(fTarget->fSlideRoot);
Florin Malita836f8222018-02-20 10:31:01 -0500146 fDir->fRoot->addChild(fShade);
Florin Malita919e2092019-01-09 15:37:57 -0500147 fDir->fRoot->addChild(fTarget->fSlideRoot);
Florin Malita836f8222018-02-20 10:31:01 -0500148
Florin Malita65fce9e2018-02-19 13:25:18 -0500149 fM0 = SlideMatrix(fTarget->fSlide, fTarget->fRect);
150 fM1 = SlideMatrix(fTarget->fSlide, fRect);
151
Florin Malita836f8222018-02-20 10:31:01 -0500152 fOpacity0 = 0;
153 fOpacity1 = 1;
154
Florin Malita65fce9e2018-02-19 13:25:18 -0500155 fTimeBase = 0;
156 fState = State::kFocusing;
Florin Malita836f8222018-02-20 10:31:01 -0500157
158 // Push initial state to the scene graph.
159 this->onTick(fTimeBase);
Florin Malita65fce9e2018-02-19 13:25:18 -0500160 }
161
162 void startUnfocus() {
163 SkASSERT(fTarget);
164
Ben Wagnerf08d1d02018-06-18 15:11:00 -0400165 using std::swap;
166 swap(fM0, fM1);
167 swap(fOpacity0, fOpacity1);
Florin Malita65fce9e2018-02-19 13:25:18 -0500168
169 fTimeBase = 0;
170 fState = State::kUnfocusing;
171 }
172
Hal Canaryb1f411a2019-08-29 10:39:22 -0400173 bool onMouse(SkScalar x, SkScalar y, skui::InputState state, skui::ModifierKey modifiers) {
Florin Malita65fce9e2018-02-19 13:25:18 -0500174 SkASSERT(fTarget);
175
Florin Malitaeb420452018-02-20 11:44:43 -0500176 if (!fRect.contains(x, y)) {
Florin Malita836f8222018-02-20 10:31:01 -0500177 this->startUnfocus();
178 return true;
179 }
180
Florin Malita65fce9e2018-02-19 13:25:18 -0500181 // Map coords to slide space.
Mike Reed2ac6ce82021-01-15 12:26:22 -0500182 const auto xform = SkMatrix::RectToRect(fRect, SkRect::MakeSize(fDir->fWinSize),
183 SkMatrix::kCenter_ScaleToFit);
Florin Malita65fce9e2018-02-19 13:25:18 -0500184 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:
Brian Salomond0072812020-07-21 17:03:56 -0400196 void onTick(float t) override {
Florin Malita65fce9e2018-02-19 13:25:18 -0500197 if (!this->isAnimating())
198 return;
199
200 if (!fTimeBase) {
201 fTimeBase = t;
202 }
203
Florin Malita836f8222018-02-20 10:31:01 -0500204 const auto rel_t = (t - fTimeBase) / kFocusDuration,
205 map_t = SkTPin(fMap.computeYFromX(rel_t), 0.0f, 1.0f);
Florin Malita65fce9e2018-02-19 13:25:18 -0500206
207 SkMatrix m;
208 for (int i = 0; i < 9; ++i) {
Florin Malita836f8222018-02-20 10:31:01 -0500209 m[i] = fM0[i] + map_t * (fM1[i] - fM0[i]);
Florin Malita65fce9e2018-02-19 13:25:18 -0500210 }
211
212 SkASSERT(fTarget);
Florin Malita919e2092019-01-09 15:37:57 -0500213 fTarget->fMatrix->setMatrix(m);
Florin Malita65fce9e2018-02-19 13:25:18 -0500214
Florin Malita836f8222018-02-20 10:31:01 -0500215 const auto shadeOpacity = fOpacity0 + map_t * (fOpacity1 - fOpacity0);
216 fShadePaint->setOpacity(shadeOpacity);
217
Florin Malita65fce9e2018-02-19 13:25:18 -0500218 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;
Florin Malita836f8222018-02-20 10:31:01 -0500227 fDir->fRoot->removeChild(fShade);
Florin Malita65fce9e2018-02-19 13:25:18 -0500228 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
Florin Malita836f8222018-02-20 10:31:01 -0500247 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;
Florin Malita65fce9e2018-02-19 13:25:18 -0500254
255 SkMatrix fM0 = SkMatrix::I(),
256 fM1 = SkMatrix::I();
Florin Malita836f8222018-02-20 10:31:01 -0500257 float fOpacity0 = 0,
258 fOpacity1 = 1,
259 fTimeBase = 0;
Florin Malita65fce9e2018-02-19 13:25:18 -0500260 State fState = State::kIdle;
261
Florin Malitabb7d95f2020-03-26 15:58:56 -0400262 using INHERITED = Animator;
Florin Malita76a076b2018-02-15 18:40:48 -0500263};
264
Brian Salomon343553a2018-09-05 15:41:23 -0400265SlideDir::SlideDir(const SkString& name, SkTArray<sk_sp<Slide>>&& slides, int columns)
Florin Malita76a076b2018-02-15 18:40:48 -0500266 : fSlides(std::move(slides))
267 , fColumns(columns) {
268 fName = name;
269}
270
Florin Malita65fce9e2018-02-19 13:25:18 -0500271static 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());
Florin Malita76a076b2018-02-15 18:40:48 -0500275 auto text = sksg::Text::Make(nullptr, txt);
Florin Malitaf7d6ac12018-11-21 16:03:58 -0500276 text->setEdging(SkFont::Edging::kAntiAlias);
Florin Malita65fce9e2018-02-19 13:25:18 -0500277 text->setSize(size);
Mike Reed3a42ec02018-10-30 12:53:21 -0400278 text->setAlign(SkTextUtils::kCenter_Align);
Florin Malita65fce9e2018-02-19 13:25:18 -0500279 text->setPosition(pos + SkPoint::Make(0, size));
Florin Malita76a076b2018-02-15 18:40:48 -0500280
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
Florin Malita65fce9e2018-02-19 13:25:18 -0500303 fWinSize = SkSize::Make(winWidth, winHeight);
304 const auto cellWidth = winWidth / fColumns;
305 fCellSize = SkSize::Make(cellWidth, cellWidth / kAspectRatio);
Florin Malita76a076b2018-02-15 18:40:48 -0500306
Florin Malita65fce9e2018-02-19 13:25:18 -0500307 fRoot = sksg::Group::Make();
Florin Malita76a076b2018-02-15 18:40:48 -0500308
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();
Florin Malita65fce9e2018-02-19 13:25:18 -0500314 const auto cell = SkRect::MakeXYWH(fCellSize.width() * (i % fColumns),
315 fCellSize.height() * (i / fColumns),
316 fCellSize.width(),
317 fCellSize.height()),
Florin Malita76a076b2018-02-15 18:40:48 -0500318 slideRect = cell.makeInset(kPadding.width(), kPadding.height());
319
Florin Malita760a0522019-01-10 15:24:15 -0500320 auto slideMatrix = sksg::Matrix<SkMatrix>::Make(SlideMatrix(slide, slideRect));
Florin Malita65fce9e2018-02-19 13:25:18 -0500321 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()),
Florin Malita919e2092019-01-09 15:37:57 -0500329 slideMatrix->getMatrix()));
330 auto slideRoot = sksg::TransformEffect::Make(std::move(slideGrp), slideMatrix);
Florin Malita76a076b2018-02-15 18:40:48 -0500331
Florin Malitabb7d95f2020-03-26 15:58:56 -0400332 fSceneAnimators.push_back(adapter->makeForwardingAnimator());
Florin Malita76a076b2018-02-15 18:40:48 -0500333
Florin Malita919e2092019-01-09 15:37:57 -0500334 fRoot->addChild(slideRoot);
335 fRecs.push_back({ slide, slideRoot, slideMatrix, slideRect });
Florin Malita76a076b2018-02-15 18:40:48 -0500336 }
337
Florin Malitabb7d95f2020-03-26 15:58:56 -0400338 fScene = sksg::Scene::Make(fRoot);
Florin Malita65fce9e2018-02-19 13:25:18 -0500339
340 const auto focusRect = SkRect::MakeSize(fWinSize).makeInset(kFocusInset.width(),
341 kFocusInset.height());
Mike Kleinf46d5ca2019-12-11 10:45:01 -0500342 fFocusController = std::make_unique<FocusController>(this, focusRect);
Florin Malita76a076b2018-02-15 18:40:48 -0500343}
344
345void SlideDir::unload() {
346 for (const auto& slide : fSlides) {
347 slide->unload();
348 }
349
350 fRecs.reset();
351 fScene.reset();
Florin Malita65fce9e2018-02-19 13:25:18 -0500352 fFocusController.reset();
353 fRoot.reset();
Florin Malita76a076b2018-02-15 18:40:48 -0500354 fTimeBase = 0;
355}
356
357SkISize SlideDir::getDimensions() const {
Florin Malita60d3bfc2018-02-20 16:49:20 -0500358 return SkSize::Make(fWinSize.width(),
359 fCellSize.height() * (1 + (fSlides.count() - 1) / fColumns)).toCeil();
Florin Malita76a076b2018-02-15 18:40:48 -0500360}
361
362void SlideDir::draw(SkCanvas* canvas) {
363 fScene->render(canvas);
364}
365
Hal Canary41248072019-07-11 16:32:53 -0400366bool SlideDir::animate(double nanos) {
367 SkMSec msec = TimeUtils::NanosToMSec(nanos);
Florin Malita76a076b2018-02-15 18:40:48 -0500368 if (fTimeBase == 0) {
369 // Reset the animation time.
Hal Canary41248072019-07-11 16:32:53 -0400370 fTimeBase = msec;
Florin Malita76a076b2018-02-15 18:40:48 -0500371 }
Florin Malita65fce9e2018-02-19 13:25:18 -0500372
Hal Canary41248072019-07-11 16:32:53 -0400373 const auto t = msec - fTimeBase;
Florin Malitabb7d95f2020-03-26 15:58:56 -0400374 for (const auto& anim : fSceneAnimators) {
375 anim->tick(t);
376 }
Florin Malita65fce9e2018-02-19 13:25:18 -0500377 fFocusController->tick(t);
Florin Malita76a076b2018-02-15 18:40:48 -0500378
379 return true;
380}
381
382bool SlideDir::onChar(SkUnichar c) {
Florin Malita65fce9e2018-02-19 13:25:18 -0500383 if (fFocusController->hasFocus()) {
384 if (c == kUnfocusKey) {
385 fFocusController->startUnfocus();
386 return true;
387 }
388 return fFocusController->onChar(c);
389 }
390
Florin Malita76a076b2018-02-15 18:40:48 -0500391 return false;
392}
393
Hal Canaryb1f411a2019-08-29 10:39:22 -0400394bool SlideDir::onMouse(SkScalar x, SkScalar y, skui::InputState state,
395 skui::ModifierKey modifiers) {
396 modifiers &= ~skui::ModifierKey::kFirstPress;
Mike Kleindc976a92020-04-30 06:45:25 -0500397 if (state == skui::InputState::kMove || sknonstd::Any(modifiers))
Florin Malita65fce9e2018-02-19 13:25:18 -0500398 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) {
Hal Canaryb1f411a2019-08-29 10:39:22 -0400411 case skui::InputState::kDown:
Florin Malita65fce9e2018-02-19 13:25:18 -0500412 fTrackingCell = cell;
413 fTrackingPos = SkPoint::Make(x, y);
414 break;
Hal Canaryb1f411a2019-08-29 10:39:22 -0400415 case skui::InputState::kUp:
Florin Malita65fce9e2018-02-19 13:25:18 -0500416 if (cell == fTrackingCell &&
417 SkPoint::Distance(fTrackingPos, SkPoint::Make(x, y)) < kClickMoveTolerance) {
Florin Malita65fce9e2018-02-19 13:25:18 -0500418 fFocusController->startFocus(cell);
419 }
420 break;
421 default:
422 break;
423 }
424
Florin Malita76a076b2018-02-15 18:40:48 -0500425 return false;
426}
Florin Malita65fce9e2018-02-19 13:25:18 -0500427
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
Florin Malita60d3bfc2018-02-20 16:49:20 -0500439 return idx < fRecs.count() ? &fRecs[idx] : nullptr;
Florin Malita65fce9e2018-02-19 13:25:18 -0500440}