blob: 95b1f4a1e2fd376bffe9bdf4f54ac86d3bf75031 [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:
29 virtual ~Animator() = default;
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
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();
111 return SkMatrix::MakeRectToRect(SkRect::MakeIWH(slideSize.width(), slideSize.height()),
112 dst,
113 SkMatrix::kCenter_ScaleToFit);
114}
115
Florin Malita76a076b2018-02-15 18:40:48 -0500116} // namespace
117
118struct SlideDir::Rec {
Florin Malita760a0522019-01-10 15:24:15 -0500119 sk_sp<Slide> fSlide;
120 sk_sp<sksg::RenderNode> fSlideRoot;
121 sk_sp<sksg::Matrix<SkMatrix>> fMatrix;
122 SkRect fRect;
Florin Malita65fce9e2018-02-19 13:25:18 -0500123};
124
Florin Malitabb7d95f2020-03-26 15:58:56 -0400125class SlideDir::FocusController final : public Animator {
Florin Malita65fce9e2018-02-19 13:25:18 -0500126public:
127 FocusController(const SlideDir* dir, const SkRect& focusRect)
128 : fDir(dir)
129 , fRect(focusRect)
130 , fTarget(nullptr)
Florin Malita34336e32019-03-06 16:34:21 -0500131 , fMap(kFocusCtrl1, kFocusCtrl0)
Florin Malita836f8222018-02-20 10:31:01 -0500132 , fState(State::kIdle) {
Florin Malita836f8222018-02-20 10:31:01 -0500133 fShadePaint = sksg::Color::Make(kFocusShade);
Florin Malita97b3d2b2018-02-20 11:26:15 -0500134 fShade = sksg::Draw::Make(sksg::Plane::Make(), fShadePaint);
Florin Malita836f8222018-02-20 10:31:01 -0500135 }
Florin Malita65fce9e2018-02-19 13:25:18 -0500136
137 bool hasFocus() const { return fState == State::kFocused; }
138
139 void startFocus(const Rec* target) {
Florin Malita836f8222018-02-20 10:31:01 -0500140 if (fState != State::kIdle)
141 return;
142
Florin Malita65fce9e2018-02-19 13:25:18 -0500143 fTarget = target;
Florin Malita836f8222018-02-20 10:31:01 -0500144
145 // Move the shade & slide to front.
Florin Malita919e2092019-01-09 15:37:57 -0500146 fDir->fRoot->removeChild(fTarget->fSlideRoot);
Florin Malita836f8222018-02-20 10:31:01 -0500147 fDir->fRoot->addChild(fShade);
Florin Malita919e2092019-01-09 15:37:57 -0500148 fDir->fRoot->addChild(fTarget->fSlideRoot);
Florin Malita836f8222018-02-20 10:31:01 -0500149
Florin Malita65fce9e2018-02-19 13:25:18 -0500150 fM0 = SlideMatrix(fTarget->fSlide, fTarget->fRect);
151 fM1 = SlideMatrix(fTarget->fSlide, fRect);
152
Florin Malita836f8222018-02-20 10:31:01 -0500153 fOpacity0 = 0;
154 fOpacity1 = 1;
155
Florin Malita65fce9e2018-02-19 13:25:18 -0500156 fTimeBase = 0;
157 fState = State::kFocusing;
Florin Malita836f8222018-02-20 10:31:01 -0500158
159 // Push initial state to the scene graph.
160 this->onTick(fTimeBase);
Florin Malita65fce9e2018-02-19 13:25:18 -0500161 }
162
163 void startUnfocus() {
164 SkASSERT(fTarget);
165
Ben Wagnerf08d1d02018-06-18 15:11:00 -0400166 using std::swap;
167 swap(fM0, fM1);
168 swap(fOpacity0, fOpacity1);
Florin Malita65fce9e2018-02-19 13:25:18 -0500169
170 fTimeBase = 0;
171 fState = State::kUnfocusing;
172 }
173
Hal Canaryb1f411a2019-08-29 10:39:22 -0400174 bool onMouse(SkScalar x, SkScalar y, skui::InputState state, skui::ModifierKey modifiers) {
Florin Malita65fce9e2018-02-19 13:25:18 -0500175 SkASSERT(fTarget);
176
Florin Malitaeb420452018-02-20 11:44:43 -0500177 if (!fRect.contains(x, y)) {
Florin Malita836f8222018-02-20 10:31:01 -0500178 this->startUnfocus();
179 return true;
180 }
181
Florin Malita65fce9e2018-02-19 13:25:18 -0500182 // Map coords to slide space.
183 const auto xform = SkMatrix::MakeRectToRect(fRect,
184 SkRect::MakeSize(fDir->fWinSize),
185 SkMatrix::kCenter_ScaleToFit);
186 const auto pt = xform.mapXY(x, y);
187
188 return fTarget->fSlide->onMouse(pt.x(), pt.y(), state, modifiers);
189 }
190
191 bool onChar(SkUnichar c) {
192 SkASSERT(fTarget);
193
194 return fTarget->fSlide->onChar(c);
195 }
196
197protected:
198 void onTick(float t) {
199 if (!this->isAnimating())
200 return;
201
202 if (!fTimeBase) {
203 fTimeBase = t;
204 }
205
Florin Malita836f8222018-02-20 10:31:01 -0500206 const auto rel_t = (t - fTimeBase) / kFocusDuration,
207 map_t = SkTPin(fMap.computeYFromX(rel_t), 0.0f, 1.0f);
Florin Malita65fce9e2018-02-19 13:25:18 -0500208
209 SkMatrix m;
210 for (int i = 0; i < 9; ++i) {
Florin Malita836f8222018-02-20 10:31:01 -0500211 m[i] = fM0[i] + map_t * (fM1[i] - fM0[i]);
Florin Malita65fce9e2018-02-19 13:25:18 -0500212 }
213
214 SkASSERT(fTarget);
Florin Malita919e2092019-01-09 15:37:57 -0500215 fTarget->fMatrix->setMatrix(m);
Florin Malita65fce9e2018-02-19 13:25:18 -0500216
Florin Malita836f8222018-02-20 10:31:01 -0500217 const auto shadeOpacity = fOpacity0 + map_t * (fOpacity1 - fOpacity0);
218 fShadePaint->setOpacity(shadeOpacity);
219
Florin Malita65fce9e2018-02-19 13:25:18 -0500220 if (rel_t < 1)
221 return;
222
223 switch (fState) {
224 case State::kFocusing:
225 fState = State::kFocused;
226 break;
227 case State::kUnfocusing:
228 fState = State::kIdle;
Florin Malita836f8222018-02-20 10:31:01 -0500229 fDir->fRoot->removeChild(fShade);
Florin Malita65fce9e2018-02-19 13:25:18 -0500230 break;
231
232 case State::kIdle:
233 case State::kFocused:
234 SkASSERT(false);
235 break;
236 }
237 }
238
239private:
240 enum class State {
241 kIdle,
242 kFocusing,
243 kUnfocusing,
244 kFocused,
245 };
246
247 bool isAnimating() const { return fState == State::kFocusing || fState == State::kUnfocusing; }
248
Florin Malita836f8222018-02-20 10:31:01 -0500249 const SlideDir* fDir;
250 const SkRect fRect;
251 const Rec* fTarget;
252
253 SkCubicMap fMap;
254 sk_sp<sksg::RenderNode> fShade;
255 sk_sp<sksg::PaintNode> fShadePaint;
Florin Malita65fce9e2018-02-19 13:25:18 -0500256
257 SkMatrix fM0 = SkMatrix::I(),
258 fM1 = SkMatrix::I();
Florin Malita836f8222018-02-20 10:31:01 -0500259 float fOpacity0 = 0,
260 fOpacity1 = 1,
261 fTimeBase = 0;
Florin Malita65fce9e2018-02-19 13:25:18 -0500262 State fState = State::kIdle;
263
Florin Malitabb7d95f2020-03-26 15:58:56 -0400264 using INHERITED = Animator;
Florin Malita76a076b2018-02-15 18:40:48 -0500265};
266
Brian Salomon343553a2018-09-05 15:41:23 -0400267SlideDir::SlideDir(const SkString& name, SkTArray<sk_sp<Slide>>&& slides, int columns)
Florin Malita76a076b2018-02-15 18:40:48 -0500268 : fSlides(std::move(slides))
269 , fColumns(columns) {
270 fName = name;
271}
272
Florin Malita65fce9e2018-02-19 13:25:18 -0500273static sk_sp<sksg::RenderNode> MakeLabel(const SkString& txt,
274 const SkPoint& pos,
275 const SkMatrix& dstXform) {
276 const auto size = kLabelSize / std::sqrt(dstXform.getScaleX() * dstXform.getScaleY());
Florin Malita76a076b2018-02-15 18:40:48 -0500277 auto text = sksg::Text::Make(nullptr, txt);
Florin Malitaf7d6ac12018-11-21 16:03:58 -0500278 text->setEdging(SkFont::Edging::kAntiAlias);
Florin Malita65fce9e2018-02-19 13:25:18 -0500279 text->setSize(size);
Mike Reed3a42ec02018-10-30 12:53:21 -0400280 text->setAlign(SkTextUtils::kCenter_Align);
Florin Malita65fce9e2018-02-19 13:25:18 -0500281 text->setPosition(pos + SkPoint::Make(0, size));
Florin Malita76a076b2018-02-15 18:40:48 -0500282
283 return sksg::Draw::Make(std::move(text), sksg::Color::Make(SK_ColorBLACK));
284}
285
286void SlideDir::load(SkScalar winWidth, SkScalar winHeight) {
287 // Build a global scene using transformed animation fragments:
288 //
289 // [Group(root)]
290 // [Transform]
291 // [Group]
292 // [AnimationWrapper]
293 // [Draw]
294 // [Text]
295 // [Color]
296 // [Transform]
297 // [Group]
298 // [AnimationWrapper]
299 // [Draw]
300 // [Text]
301 // [Color]
302 // ...
303 //
304
Florin Malita65fce9e2018-02-19 13:25:18 -0500305 fWinSize = SkSize::Make(winWidth, winHeight);
306 const auto cellWidth = winWidth / fColumns;
307 fCellSize = SkSize::Make(cellWidth, cellWidth / kAspectRatio);
Florin Malita76a076b2018-02-15 18:40:48 -0500308
Florin Malita65fce9e2018-02-19 13:25:18 -0500309 fRoot = sksg::Group::Make();
Florin Malita76a076b2018-02-15 18:40:48 -0500310
311 for (int i = 0; i < fSlides.count(); ++i) {
312 const auto& slide = fSlides[i];
313 slide->load(winWidth, winHeight);
314
315 const auto slideSize = slide->getDimensions();
Florin Malita65fce9e2018-02-19 13:25:18 -0500316 const auto cell = SkRect::MakeXYWH(fCellSize.width() * (i % fColumns),
317 fCellSize.height() * (i / fColumns),
318 fCellSize.width(),
319 fCellSize.height()),
Florin Malita76a076b2018-02-15 18:40:48 -0500320 slideRect = cell.makeInset(kPadding.width(), kPadding.height());
321
Florin Malita760a0522019-01-10 15:24:15 -0500322 auto slideMatrix = sksg::Matrix<SkMatrix>::Make(SlideMatrix(slide, slideRect));
Florin Malita65fce9e2018-02-19 13:25:18 -0500323 auto adapter = sk_make_sp<SlideAdapter>(slide);
324 auto slideGrp = sksg::Group::Make();
325 slideGrp->addChild(sksg::Draw::Make(sksg::Rect::Make(SkRect::MakeIWH(slideSize.width(),
326 slideSize.height())),
327 sksg::Color::Make(0xfff0f0f0)));
328 slideGrp->addChild(adapter);
329 slideGrp->addChild(MakeLabel(slide->getName(),
330 SkPoint::Make(slideSize.width() / 2, slideSize.height()),
Florin Malita919e2092019-01-09 15:37:57 -0500331 slideMatrix->getMatrix()));
332 auto slideRoot = sksg::TransformEffect::Make(std::move(slideGrp), slideMatrix);
Florin Malita76a076b2018-02-15 18:40:48 -0500333
Florin Malitabb7d95f2020-03-26 15:58:56 -0400334 fSceneAnimators.push_back(adapter->makeForwardingAnimator());
Florin Malita76a076b2018-02-15 18:40:48 -0500335
Florin Malita919e2092019-01-09 15:37:57 -0500336 fRoot->addChild(slideRoot);
337 fRecs.push_back({ slide, slideRoot, slideMatrix, slideRect });
Florin Malita76a076b2018-02-15 18:40:48 -0500338 }
339
Florin Malitabb7d95f2020-03-26 15:58:56 -0400340 fScene = sksg::Scene::Make(fRoot);
Florin Malita65fce9e2018-02-19 13:25:18 -0500341
342 const auto focusRect = SkRect::MakeSize(fWinSize).makeInset(kFocusInset.width(),
343 kFocusInset.height());
Mike Kleinf46d5ca2019-12-11 10:45:01 -0500344 fFocusController = std::make_unique<FocusController>(this, focusRect);
Florin Malita76a076b2018-02-15 18:40:48 -0500345}
346
347void SlideDir::unload() {
348 for (const auto& slide : fSlides) {
349 slide->unload();
350 }
351
352 fRecs.reset();
353 fScene.reset();
Florin Malita65fce9e2018-02-19 13:25:18 -0500354 fFocusController.reset();
355 fRoot.reset();
Florin Malita76a076b2018-02-15 18:40:48 -0500356 fTimeBase = 0;
357}
358
359SkISize SlideDir::getDimensions() const {
Florin Malita60d3bfc2018-02-20 16:49:20 -0500360 return SkSize::Make(fWinSize.width(),
361 fCellSize.height() * (1 + (fSlides.count() - 1) / fColumns)).toCeil();
Florin Malita76a076b2018-02-15 18:40:48 -0500362}
363
364void SlideDir::draw(SkCanvas* canvas) {
365 fScene->render(canvas);
366}
367
Hal Canary41248072019-07-11 16:32:53 -0400368bool SlideDir::animate(double nanos) {
369 SkMSec msec = TimeUtils::NanosToMSec(nanos);
Florin Malita76a076b2018-02-15 18:40:48 -0500370 if (fTimeBase == 0) {
371 // Reset the animation time.
Hal Canary41248072019-07-11 16:32:53 -0400372 fTimeBase = msec;
Florin Malita76a076b2018-02-15 18:40:48 -0500373 }
Florin Malita65fce9e2018-02-19 13:25:18 -0500374
Hal Canary41248072019-07-11 16:32:53 -0400375 const auto t = msec - fTimeBase;
Florin Malitabb7d95f2020-03-26 15:58:56 -0400376 for (const auto& anim : fSceneAnimators) {
377 anim->tick(t);
378 }
Florin Malita65fce9e2018-02-19 13:25:18 -0500379 fFocusController->tick(t);
Florin Malita76a076b2018-02-15 18:40:48 -0500380
381 return true;
382}
383
384bool SlideDir::onChar(SkUnichar c) {
Florin Malita65fce9e2018-02-19 13:25:18 -0500385 if (fFocusController->hasFocus()) {
386 if (c == kUnfocusKey) {
387 fFocusController->startUnfocus();
388 return true;
389 }
390 return fFocusController->onChar(c);
391 }
392
Florin Malita76a076b2018-02-15 18:40:48 -0500393 return false;
394}
395
Hal Canaryb1f411a2019-08-29 10:39:22 -0400396bool SlideDir::onMouse(SkScalar x, SkScalar y, skui::InputState state,
397 skui::ModifierKey modifiers) {
398 modifiers &= ~skui::ModifierKey::kFirstPress;
399 if (state == skui::InputState::kMove || skstd::Any(modifiers))
Florin Malita65fce9e2018-02-19 13:25:18 -0500400 return false;
401
402 if (fFocusController->hasFocus()) {
403 return fFocusController->onMouse(x, y, state, modifiers);
404 }
405
406 const auto* cell = this->findCell(x, y);
407 if (!cell)
408 return false;
409
410 static constexpr SkScalar kClickMoveTolerance = 4;
411
412 switch (state) {
Hal Canaryb1f411a2019-08-29 10:39:22 -0400413 case skui::InputState::kDown:
Florin Malita65fce9e2018-02-19 13:25:18 -0500414 fTrackingCell = cell;
415 fTrackingPos = SkPoint::Make(x, y);
416 break;
Hal Canaryb1f411a2019-08-29 10:39:22 -0400417 case skui::InputState::kUp:
Florin Malita65fce9e2018-02-19 13:25:18 -0500418 if (cell == fTrackingCell &&
419 SkPoint::Distance(fTrackingPos, SkPoint::Make(x, y)) < kClickMoveTolerance) {
Florin Malita65fce9e2018-02-19 13:25:18 -0500420 fFocusController->startFocus(cell);
421 }
422 break;
423 default:
424 break;
425 }
426
Florin Malita76a076b2018-02-15 18:40:48 -0500427 return false;
428}
Florin Malita65fce9e2018-02-19 13:25:18 -0500429
430const SlideDir::Rec* SlideDir::findCell(float x, float y) const {
431 // TODO: use SG hit testing instead of layout info?
432 const auto size = this->getDimensions();
433 if (x < 0 || y < 0 || x >= size.width() || y >= size.height()) {
434 return nullptr;
435 }
436
437 const int col = static_cast<int>(x / fCellSize.width()),
438 row = static_cast<int>(y / fCellSize.height()),
439 idx = row * fColumns + col;
440
Florin Malita60d3bfc2018-02-20 16:49:20 -0500441 return idx < fRecs.count() ? &fRecs[idx] : nullptr;
Florin Malita65fce9e2018-02-19 13:25:18 -0500442}