blob: 5cce6b1f28364a2ff69f2e3a6b06f25743e131c4 [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"
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"
Hal Canary41248072019-07-11 16:32:53 -040022#include "tools/timer/TimeUtils.h"
Florin Malita76a076b2018-02-15 18:40:48 -050023
Florin Malita65fce9e2018-02-19 13:25:18 -050024#include <cmath>
Ben Wagnerf08d1d02018-06-18 15:11:00 -040025#include <utility>
Florin Malita65fce9e2018-02-19 13:25:18 -050026
Florin Malitabb7d95f2020-03-26 15:58:56 -040027class SlideDir::Animator : public SkRefCnt {
28public:
Florin Malitabb7d95f2020-03-26 15:58:56 -040029 Animator(const Animator&) = delete;
30 Animator& operator=(const Animator&) = delete;
31
32 void tick(float t) { this->onTick(t); }
33
34protected:
35 Animator() = default;
36
37 virtual void onTick(float t) = 0;
38};
39
Florin Malita76a076b2018-02-15 18:40:48 -050040namespace {
41
Florin Malita65fce9e2018-02-19 13:25:18 -050042static constexpr float kAspectRatio = 1.5f;
43static constexpr float kLabelSize = 12.0f;
44static constexpr SkSize kPadding = { 12.0f , 24.0f };
Florin Malita836f8222018-02-20 10:31:01 -050045
46static constexpr float kFocusDuration = 500;
47static constexpr SkSize kFocusInset = { 100.0f, 100.0f };
48static constexpr SkPoint kFocusCtrl0 = { 0.3f, 1.0f };
49static constexpr SkPoint kFocusCtrl1 = { 0.0f, 1.0f };
50static constexpr SkColor kFocusShade = 0xa0000000;
Florin Malita65fce9e2018-02-19 13:25:18 -050051
52// TODO: better unfocus binding?
53static constexpr SkUnichar kUnfocusKey = ' ';
Florin Malita76a076b2018-02-15 18:40:48 -050054
55class SlideAdapter final : public sksg::RenderNode {
56public:
57 explicit SlideAdapter(sk_sp<Slide> slide)
58 : fSlide(std::move(slide)) {
59 SkASSERT(fSlide);
60 }
61
Florin Malitabb7d95f2020-03-26 15:58:56 -040062 sk_sp<SlideDir::Animator> makeForwardingAnimator() {
Florin Malita76a076b2018-02-15 18:40:48 -050063 // Trivial sksg::Animator -> skottie::Animation tick adapter
Florin Malitabb7d95f2020-03-26 15:58:56 -040064 class ForwardingAnimator final : public SlideDir::Animator {
Florin Malita76a076b2018-02-15 18:40:48 -050065 public:
66 explicit ForwardingAnimator(sk_sp<SlideAdapter> adapter)
67 : fAdapter(std::move(adapter)) {}
68
69 protected:
70 void onTick(float t) override {
71 fAdapter->tick(SkScalarRoundToInt(t));
72 }
73
74 private:
75 sk_sp<SlideAdapter> fAdapter;
76 };
77
Florin Malita5f240182019-07-23 17:28:53 -040078 return sk_make_sp<ForwardingAnimator>(sk_ref_sp(this));
Florin Malita76a076b2018-02-15 18:40:48 -050079 }
80
81protected:
82 SkRect onRevalidate(sksg::InvalidationController* ic, const SkMatrix& ctm) override {
83 const auto isize = fSlide->getDimensions();
84 return SkRect::MakeIWH(isize.width(), isize.height());
85 }
86
Florin Malitac0132ff2018-08-09 07:40:01 -040087 void onRender(SkCanvas* canvas, const RenderContext* ctx) const override {
Florin Malita6127c4c2018-04-05 15:15:59 -040088 SkAutoCanvasRestore acr(canvas, true);
89 canvas->clipRect(SkRect::Make(fSlide->getDimensions()), true);
Florin Malitac0132ff2018-08-09 07:40:01 -040090
91 // TODO: commit the context?
Florin Malita76a076b2018-02-15 18:40:48 -050092 fSlide->draw(canvas);
93 }
94
Florin Malitaeb46bd82019-02-12 09:33:21 -050095 const RenderNode* onNodeAt(const SkPoint&) const override { return nullptr; }
96
Florin Malita76a076b2018-02-15 18:40:48 -050097private:
98 void tick(SkMSec t) {
Hal Canary41248072019-07-11 16:32:53 -040099 fSlide->animate(t * 1e6);
Florin Malita76a076b2018-02-15 18:40:48 -0500100 this->invalidate();
101 }
102
103 const sk_sp<Slide> fSlide;
104
105 using INHERITED = sksg::RenderNode;
106};
107
Florin Malita65fce9e2018-02-19 13:25:18 -0500108SkMatrix SlideMatrix(const sk_sp<Slide>& slide, const SkRect& dst) {
109 const auto slideSize = slide->getDimensions();
110 return SkMatrix::MakeRectToRect(SkRect::MakeIWH(slideSize.width(), slideSize.height()),
111 dst,
112 SkMatrix::kCenter_ScaleToFit);
113}
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.
182 const auto xform = SkMatrix::MakeRectToRect(fRect,
183 SkRect::MakeSize(fDir->fWinSize),
184 SkMatrix::kCenter_ScaleToFit);
185 const auto pt = xform.mapXY(x, y);
186
187 return fTarget->fSlide->onMouse(pt.x(), pt.y(), state, modifiers);
188 }
189
190 bool onChar(SkUnichar c) {
191 SkASSERT(fTarget);
192
193 return fTarget->fSlide->onChar(c);
194 }
195
196protected:
Brian Salomond0072812020-07-21 17:03:56 -0400197 void onTick(float t) override {
Florin Malita65fce9e2018-02-19 13:25:18 -0500198 if (!this->isAnimating())
199 return;
200
201 if (!fTimeBase) {
202 fTimeBase = t;
203 }
204
Florin Malita836f8222018-02-20 10:31:01 -0500205 const auto rel_t = (t - fTimeBase) / kFocusDuration,
206 map_t = SkTPin(fMap.computeYFromX(rel_t), 0.0f, 1.0f);
Florin Malita65fce9e2018-02-19 13:25:18 -0500207
208 SkMatrix m;
209 for (int i = 0; i < 9; ++i) {
Florin Malita836f8222018-02-20 10:31:01 -0500210 m[i] = fM0[i] + map_t * (fM1[i] - fM0[i]);
Florin Malita65fce9e2018-02-19 13:25:18 -0500211 }
212
213 SkASSERT(fTarget);
Florin Malita919e2092019-01-09 15:37:57 -0500214 fTarget->fMatrix->setMatrix(m);
Florin Malita65fce9e2018-02-19 13:25:18 -0500215
Florin Malita836f8222018-02-20 10:31:01 -0500216 const auto shadeOpacity = fOpacity0 + map_t * (fOpacity1 - fOpacity0);
217 fShadePaint->setOpacity(shadeOpacity);
218
Florin Malita65fce9e2018-02-19 13:25:18 -0500219 if (rel_t < 1)
220 return;
221
222 switch (fState) {
223 case State::kFocusing:
224 fState = State::kFocused;
225 break;
226 case State::kUnfocusing:
227 fState = State::kIdle;
Florin Malita836f8222018-02-20 10:31:01 -0500228 fDir->fRoot->removeChild(fShade);
Florin Malita65fce9e2018-02-19 13:25:18 -0500229 break;
230
231 case State::kIdle:
232 case State::kFocused:
233 SkASSERT(false);
234 break;
235 }
236 }
237
238private:
239 enum class State {
240 kIdle,
241 kFocusing,
242 kUnfocusing,
243 kFocused,
244 };
245
246 bool isAnimating() const { return fState == State::kFocusing || fState == State::kUnfocusing; }
247
Florin Malita836f8222018-02-20 10:31:01 -0500248 const SlideDir* fDir;
249 const SkRect fRect;
250 const Rec* fTarget;
251
252 SkCubicMap fMap;
253 sk_sp<sksg::RenderNode> fShade;
254 sk_sp<sksg::PaintNode> fShadePaint;
Florin Malita65fce9e2018-02-19 13:25:18 -0500255
256 SkMatrix fM0 = SkMatrix::I(),
257 fM1 = SkMatrix::I();
Florin Malita836f8222018-02-20 10:31:01 -0500258 float fOpacity0 = 0,
259 fOpacity1 = 1,
260 fTimeBase = 0;
Florin Malita65fce9e2018-02-19 13:25:18 -0500261 State fState = State::kIdle;
262
Florin Malitabb7d95f2020-03-26 15:58:56 -0400263 using INHERITED = Animator;
Florin Malita76a076b2018-02-15 18:40:48 -0500264};
265
Brian Salomon343553a2018-09-05 15:41:23 -0400266SlideDir::SlideDir(const SkString& name, SkTArray<sk_sp<Slide>>&& slides, int columns)
Florin Malita76a076b2018-02-15 18:40:48 -0500267 : fSlides(std::move(slides))
268 , fColumns(columns) {
269 fName = name;
270}
271
Florin Malita65fce9e2018-02-19 13:25:18 -0500272static sk_sp<sksg::RenderNode> MakeLabel(const SkString& txt,
273 const SkPoint& pos,
274 const SkMatrix& dstXform) {
275 const auto size = kLabelSize / std::sqrt(dstXform.getScaleX() * dstXform.getScaleY());
Florin Malita76a076b2018-02-15 18:40:48 -0500276 auto text = sksg::Text::Make(nullptr, txt);
Florin Malitaf7d6ac12018-11-21 16:03:58 -0500277 text->setEdging(SkFont::Edging::kAntiAlias);
Florin Malita65fce9e2018-02-19 13:25:18 -0500278 text->setSize(size);
Mike Reed3a42ec02018-10-30 12:53:21 -0400279 text->setAlign(SkTextUtils::kCenter_Align);
Florin Malita65fce9e2018-02-19 13:25:18 -0500280 text->setPosition(pos + SkPoint::Make(0, size));
Florin Malita76a076b2018-02-15 18:40:48 -0500281
282 return sksg::Draw::Make(std::move(text), sksg::Color::Make(SK_ColorBLACK));
283}
284
285void SlideDir::load(SkScalar winWidth, SkScalar winHeight) {
286 // Build a global scene using transformed animation fragments:
287 //
288 // [Group(root)]
289 // [Transform]
290 // [Group]
291 // [AnimationWrapper]
292 // [Draw]
293 // [Text]
294 // [Color]
295 // [Transform]
296 // [Group]
297 // [AnimationWrapper]
298 // [Draw]
299 // [Text]
300 // [Color]
301 // ...
302 //
303
Florin Malita65fce9e2018-02-19 13:25:18 -0500304 fWinSize = SkSize::Make(winWidth, winHeight);
305 const auto cellWidth = winWidth / fColumns;
306 fCellSize = SkSize::Make(cellWidth, cellWidth / kAspectRatio);
Florin Malita76a076b2018-02-15 18:40:48 -0500307
Florin Malita65fce9e2018-02-19 13:25:18 -0500308 fRoot = sksg::Group::Make();
Florin Malita76a076b2018-02-15 18:40:48 -0500309
310 for (int i = 0; i < fSlides.count(); ++i) {
311 const auto& slide = fSlides[i];
312 slide->load(winWidth, winHeight);
313
314 const auto slideSize = slide->getDimensions();
Florin Malita65fce9e2018-02-19 13:25:18 -0500315 const auto cell = SkRect::MakeXYWH(fCellSize.width() * (i % fColumns),
316 fCellSize.height() * (i / fColumns),
317 fCellSize.width(),
318 fCellSize.height()),
Florin Malita76a076b2018-02-15 18:40:48 -0500319 slideRect = cell.makeInset(kPadding.width(), kPadding.height());
320
Florin Malita760a0522019-01-10 15:24:15 -0500321 auto slideMatrix = sksg::Matrix<SkMatrix>::Make(SlideMatrix(slide, slideRect));
Florin Malita65fce9e2018-02-19 13:25:18 -0500322 auto adapter = sk_make_sp<SlideAdapter>(slide);
323 auto slideGrp = sksg::Group::Make();
324 slideGrp->addChild(sksg::Draw::Make(sksg::Rect::Make(SkRect::MakeIWH(slideSize.width(),
325 slideSize.height())),
326 sksg::Color::Make(0xfff0f0f0)));
327 slideGrp->addChild(adapter);
328 slideGrp->addChild(MakeLabel(slide->getName(),
329 SkPoint::Make(slideSize.width() / 2, slideSize.height()),
Florin Malita919e2092019-01-09 15:37:57 -0500330 slideMatrix->getMatrix()));
331 auto slideRoot = sksg::TransformEffect::Make(std::move(slideGrp), slideMatrix);
Florin Malita76a076b2018-02-15 18:40:48 -0500332
Florin Malitabb7d95f2020-03-26 15:58:56 -0400333 fSceneAnimators.push_back(adapter->makeForwardingAnimator());
Florin Malita76a076b2018-02-15 18:40:48 -0500334
Florin Malita919e2092019-01-09 15:37:57 -0500335 fRoot->addChild(slideRoot);
336 fRecs.push_back({ slide, slideRoot, slideMatrix, slideRect });
Florin Malita76a076b2018-02-15 18:40:48 -0500337 }
338
Florin Malitabb7d95f2020-03-26 15:58:56 -0400339 fScene = sksg::Scene::Make(fRoot);
Florin Malita65fce9e2018-02-19 13:25:18 -0500340
341 const auto focusRect = SkRect::MakeSize(fWinSize).makeInset(kFocusInset.width(),
342 kFocusInset.height());
Mike Kleinf46d5ca2019-12-11 10:45:01 -0500343 fFocusController = std::make_unique<FocusController>(this, focusRect);
Florin Malita76a076b2018-02-15 18:40:48 -0500344}
345
346void SlideDir::unload() {
347 for (const auto& slide : fSlides) {
348 slide->unload();
349 }
350
351 fRecs.reset();
352 fScene.reset();
Florin Malita65fce9e2018-02-19 13:25:18 -0500353 fFocusController.reset();
354 fRoot.reset();
Florin Malita76a076b2018-02-15 18:40:48 -0500355 fTimeBase = 0;
356}
357
358SkISize SlideDir::getDimensions() const {
Florin Malita60d3bfc2018-02-20 16:49:20 -0500359 return SkSize::Make(fWinSize.width(),
360 fCellSize.height() * (1 + (fSlides.count() - 1) / fColumns)).toCeil();
Florin Malita76a076b2018-02-15 18:40:48 -0500361}
362
363void SlideDir::draw(SkCanvas* canvas) {
364 fScene->render(canvas);
365}
366
Hal Canary41248072019-07-11 16:32:53 -0400367bool SlideDir::animate(double nanos) {
368 SkMSec msec = TimeUtils::NanosToMSec(nanos);
Florin Malita76a076b2018-02-15 18:40:48 -0500369 if (fTimeBase == 0) {
370 // Reset the animation time.
Hal Canary41248072019-07-11 16:32:53 -0400371 fTimeBase = msec;
Florin Malita76a076b2018-02-15 18:40:48 -0500372 }
Florin Malita65fce9e2018-02-19 13:25:18 -0500373
Hal Canary41248072019-07-11 16:32:53 -0400374 const auto t = msec - fTimeBase;
Florin Malitabb7d95f2020-03-26 15:58:56 -0400375 for (const auto& anim : fSceneAnimators) {
376 anim->tick(t);
377 }
Florin Malita65fce9e2018-02-19 13:25:18 -0500378 fFocusController->tick(t);
Florin Malita76a076b2018-02-15 18:40:48 -0500379
380 return true;
381}
382
383bool SlideDir::onChar(SkUnichar c) {
Florin Malita65fce9e2018-02-19 13:25:18 -0500384 if (fFocusController->hasFocus()) {
385 if (c == kUnfocusKey) {
386 fFocusController->startUnfocus();
387 return true;
388 }
389 return fFocusController->onChar(c);
390 }
391
Florin Malita76a076b2018-02-15 18:40:48 -0500392 return false;
393}
394
Hal Canaryb1f411a2019-08-29 10:39:22 -0400395bool SlideDir::onMouse(SkScalar x, SkScalar y, skui::InputState state,
396 skui::ModifierKey modifiers) {
397 modifiers &= ~skui::ModifierKey::kFirstPress;
Mike Kleindc976a92020-04-30 06:45:25 -0500398 if (state == skui::InputState::kMove || sknonstd::Any(modifiers))
Florin Malita65fce9e2018-02-19 13:25:18 -0500399 return false;
400
401 if (fFocusController->hasFocus()) {
402 return fFocusController->onMouse(x, y, state, modifiers);
403 }
404
405 const auto* cell = this->findCell(x, y);
406 if (!cell)
407 return false;
408
409 static constexpr SkScalar kClickMoveTolerance = 4;
410
411 switch (state) {
Hal Canaryb1f411a2019-08-29 10:39:22 -0400412 case skui::InputState::kDown:
Florin Malita65fce9e2018-02-19 13:25:18 -0500413 fTrackingCell = cell;
414 fTrackingPos = SkPoint::Make(x, y);
415 break;
Hal Canaryb1f411a2019-08-29 10:39:22 -0400416 case skui::InputState::kUp:
Florin Malita65fce9e2018-02-19 13:25:18 -0500417 if (cell == fTrackingCell &&
418 SkPoint::Distance(fTrackingPos, SkPoint::Make(x, y)) < kClickMoveTolerance) {
Florin Malita65fce9e2018-02-19 13:25:18 -0500419 fFocusController->startFocus(cell);
420 }
421 break;
422 default:
423 break;
424 }
425
Florin Malita76a076b2018-02-15 18:40:48 -0500426 return false;
427}
Florin Malita65fce9e2018-02-19 13:25:18 -0500428
429const SlideDir::Rec* SlideDir::findCell(float x, float y) const {
430 // TODO: use SG hit testing instead of layout info?
431 const auto size = this->getDimensions();
432 if (x < 0 || y < 0 || x >= size.width() || y >= size.height()) {
433 return nullptr;
434 }
435
436 const int col = static_cast<int>(x / fCellSize.width()),
437 row = static_cast<int>(y / fCellSize.height()),
438 idx = row * fColumns + col;
439
Florin Malita60d3bfc2018-02-20 16:49:20 -0500440 return idx < fRecs.count() ? &fRecs[idx] : nullptr;
Florin Malita65fce9e2018-02-19 13:25:18 -0500441}