blob: b532d00669c3a5c674dee7c08a69433d8c6aec5d [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 Malita76a076b2018-02-15 18:40:48 -050027namespace {
28
Florin Malita65fce9e2018-02-19 13:25:18 -050029static constexpr float kAspectRatio = 1.5f;
30static constexpr float kLabelSize = 12.0f;
31static constexpr SkSize kPadding = { 12.0f , 24.0f };
Florin Malita836f8222018-02-20 10:31:01 -050032
33static constexpr float kFocusDuration = 500;
34static constexpr SkSize kFocusInset = { 100.0f, 100.0f };
35static constexpr SkPoint kFocusCtrl0 = { 0.3f, 1.0f };
36static constexpr SkPoint kFocusCtrl1 = { 0.0f, 1.0f };
37static constexpr SkColor kFocusShade = 0xa0000000;
Florin Malita65fce9e2018-02-19 13:25:18 -050038
39// TODO: better unfocus binding?
40static constexpr SkUnichar kUnfocusKey = ' ';
Florin Malita76a076b2018-02-15 18:40:48 -050041
42class SlideAdapter final : public sksg::RenderNode {
43public:
44 explicit SlideAdapter(sk_sp<Slide> slide)
45 : fSlide(std::move(slide)) {
46 SkASSERT(fSlide);
47 }
48
Florin Malita5f240182019-07-23 17:28:53 -040049 sk_sp<sksg::Animator> makeForwardingAnimator() {
Florin Malita76a076b2018-02-15 18:40:48 -050050 // Trivial sksg::Animator -> skottie::Animation tick adapter
51 class ForwardingAnimator final : public sksg::Animator {
52 public:
53 explicit ForwardingAnimator(sk_sp<SlideAdapter> adapter)
54 : fAdapter(std::move(adapter)) {}
55
56 protected:
57 void onTick(float t) override {
58 fAdapter->tick(SkScalarRoundToInt(t));
59 }
60
61 private:
62 sk_sp<SlideAdapter> fAdapter;
63 };
64
Florin Malita5f240182019-07-23 17:28:53 -040065 return sk_make_sp<ForwardingAnimator>(sk_ref_sp(this));
Florin Malita76a076b2018-02-15 18:40:48 -050066 }
67
68protected:
69 SkRect onRevalidate(sksg::InvalidationController* ic, const SkMatrix& ctm) override {
70 const auto isize = fSlide->getDimensions();
71 return SkRect::MakeIWH(isize.width(), isize.height());
72 }
73
Florin Malitac0132ff2018-08-09 07:40:01 -040074 void onRender(SkCanvas* canvas, const RenderContext* ctx) const override {
Florin Malita6127c4c2018-04-05 15:15:59 -040075 SkAutoCanvasRestore acr(canvas, true);
76 canvas->clipRect(SkRect::Make(fSlide->getDimensions()), true);
Florin Malitac0132ff2018-08-09 07:40:01 -040077
78 // TODO: commit the context?
Florin Malita76a076b2018-02-15 18:40:48 -050079 fSlide->draw(canvas);
80 }
81
Florin Malitaeb46bd82019-02-12 09:33:21 -050082 const RenderNode* onNodeAt(const SkPoint&) const override { return nullptr; }
83
Florin Malita76a076b2018-02-15 18:40:48 -050084private:
85 void tick(SkMSec t) {
Hal Canary41248072019-07-11 16:32:53 -040086 fSlide->animate(t * 1e6);
Florin Malita76a076b2018-02-15 18:40:48 -050087 this->invalidate();
88 }
89
90 const sk_sp<Slide> fSlide;
91
92 using INHERITED = sksg::RenderNode;
93};
94
Florin Malita65fce9e2018-02-19 13:25:18 -050095SkMatrix SlideMatrix(const sk_sp<Slide>& slide, const SkRect& dst) {
96 const auto slideSize = slide->getDimensions();
97 return SkMatrix::MakeRectToRect(SkRect::MakeIWH(slideSize.width(), slideSize.height()),
98 dst,
99 SkMatrix::kCenter_ScaleToFit);
100}
101
Florin Malita76a076b2018-02-15 18:40:48 -0500102} // namespace
103
104struct SlideDir::Rec {
Florin Malita760a0522019-01-10 15:24:15 -0500105 sk_sp<Slide> fSlide;
106 sk_sp<sksg::RenderNode> fSlideRoot;
107 sk_sp<sksg::Matrix<SkMatrix>> fMatrix;
108 SkRect fRect;
Florin Malita65fce9e2018-02-19 13:25:18 -0500109};
110
111class SlideDir::FocusController final : public sksg::Animator {
112public:
113 FocusController(const SlideDir* dir, const SkRect& focusRect)
114 : fDir(dir)
115 , fRect(focusRect)
116 , fTarget(nullptr)
Florin Malita34336e32019-03-06 16:34:21 -0500117 , fMap(kFocusCtrl1, kFocusCtrl0)
Florin Malita836f8222018-02-20 10:31:01 -0500118 , fState(State::kIdle) {
Florin Malita836f8222018-02-20 10:31:01 -0500119 fShadePaint = sksg::Color::Make(kFocusShade);
Florin Malita97b3d2b2018-02-20 11:26:15 -0500120 fShade = sksg::Draw::Make(sksg::Plane::Make(), fShadePaint);
Florin Malita836f8222018-02-20 10:31:01 -0500121 }
Florin Malita65fce9e2018-02-19 13:25:18 -0500122
123 bool hasFocus() const { return fState == State::kFocused; }
124
125 void startFocus(const Rec* target) {
Florin Malita836f8222018-02-20 10:31:01 -0500126 if (fState != State::kIdle)
127 return;
128
Florin Malita65fce9e2018-02-19 13:25:18 -0500129 fTarget = target;
Florin Malita836f8222018-02-20 10:31:01 -0500130
131 // Move the shade & slide to front.
Florin Malita919e2092019-01-09 15:37:57 -0500132 fDir->fRoot->removeChild(fTarget->fSlideRoot);
Florin Malita836f8222018-02-20 10:31:01 -0500133 fDir->fRoot->addChild(fShade);
Florin Malita919e2092019-01-09 15:37:57 -0500134 fDir->fRoot->addChild(fTarget->fSlideRoot);
Florin Malita836f8222018-02-20 10:31:01 -0500135
Florin Malita65fce9e2018-02-19 13:25:18 -0500136 fM0 = SlideMatrix(fTarget->fSlide, fTarget->fRect);
137 fM1 = SlideMatrix(fTarget->fSlide, fRect);
138
Florin Malita836f8222018-02-20 10:31:01 -0500139 fOpacity0 = 0;
140 fOpacity1 = 1;
141
Florin Malita65fce9e2018-02-19 13:25:18 -0500142 fTimeBase = 0;
143 fState = State::kFocusing;
Florin Malita836f8222018-02-20 10:31:01 -0500144
145 // Push initial state to the scene graph.
146 this->onTick(fTimeBase);
Florin Malita65fce9e2018-02-19 13:25:18 -0500147 }
148
149 void startUnfocus() {
150 SkASSERT(fTarget);
151
Ben Wagnerf08d1d02018-06-18 15:11:00 -0400152 using std::swap;
153 swap(fM0, fM1);
154 swap(fOpacity0, fOpacity1);
Florin Malita65fce9e2018-02-19 13:25:18 -0500155
156 fTimeBase = 0;
157 fState = State::kUnfocusing;
158 }
159
Hal Canaryb1f411a2019-08-29 10:39:22 -0400160 bool onMouse(SkScalar x, SkScalar y, skui::InputState state, skui::ModifierKey modifiers) {
Florin Malita65fce9e2018-02-19 13:25:18 -0500161 SkASSERT(fTarget);
162
Florin Malitaeb420452018-02-20 11:44:43 -0500163 if (!fRect.contains(x, y)) {
Florin Malita836f8222018-02-20 10:31:01 -0500164 this->startUnfocus();
165 return true;
166 }
167
Florin Malita65fce9e2018-02-19 13:25:18 -0500168 // Map coords to slide space.
169 const auto xform = SkMatrix::MakeRectToRect(fRect,
170 SkRect::MakeSize(fDir->fWinSize),
171 SkMatrix::kCenter_ScaleToFit);
172 const auto pt = xform.mapXY(x, y);
173
174 return fTarget->fSlide->onMouse(pt.x(), pt.y(), state, modifiers);
175 }
176
177 bool onChar(SkUnichar c) {
178 SkASSERT(fTarget);
179
180 return fTarget->fSlide->onChar(c);
181 }
182
183protected:
184 void onTick(float t) {
185 if (!this->isAnimating())
186 return;
187
188 if (!fTimeBase) {
189 fTimeBase = t;
190 }
191
Florin Malita836f8222018-02-20 10:31:01 -0500192 const auto rel_t = (t - fTimeBase) / kFocusDuration,
193 map_t = SkTPin(fMap.computeYFromX(rel_t), 0.0f, 1.0f);
Florin Malita65fce9e2018-02-19 13:25:18 -0500194
195 SkMatrix m;
196 for (int i = 0; i < 9; ++i) {
Florin Malita836f8222018-02-20 10:31:01 -0500197 m[i] = fM0[i] + map_t * (fM1[i] - fM0[i]);
Florin Malita65fce9e2018-02-19 13:25:18 -0500198 }
199
200 SkASSERT(fTarget);
Florin Malita919e2092019-01-09 15:37:57 -0500201 fTarget->fMatrix->setMatrix(m);
Florin Malita65fce9e2018-02-19 13:25:18 -0500202
Florin Malita836f8222018-02-20 10:31:01 -0500203 const auto shadeOpacity = fOpacity0 + map_t * (fOpacity1 - fOpacity0);
204 fShadePaint->setOpacity(shadeOpacity);
205
Florin Malita65fce9e2018-02-19 13:25:18 -0500206 if (rel_t < 1)
207 return;
208
209 switch (fState) {
210 case State::kFocusing:
211 fState = State::kFocused;
212 break;
213 case State::kUnfocusing:
214 fState = State::kIdle;
Florin Malita836f8222018-02-20 10:31:01 -0500215 fDir->fRoot->removeChild(fShade);
Florin Malita65fce9e2018-02-19 13:25:18 -0500216 break;
217
218 case State::kIdle:
219 case State::kFocused:
220 SkASSERT(false);
221 break;
222 }
223 }
224
225private:
226 enum class State {
227 kIdle,
228 kFocusing,
229 kUnfocusing,
230 kFocused,
231 };
232
233 bool isAnimating() const { return fState == State::kFocusing || fState == State::kUnfocusing; }
234
Florin Malita836f8222018-02-20 10:31:01 -0500235 const SlideDir* fDir;
236 const SkRect fRect;
237 const Rec* fTarget;
238
239 SkCubicMap fMap;
240 sk_sp<sksg::RenderNode> fShade;
241 sk_sp<sksg::PaintNode> fShadePaint;
Florin Malita65fce9e2018-02-19 13:25:18 -0500242
243 SkMatrix fM0 = SkMatrix::I(),
244 fM1 = SkMatrix::I();
Florin Malita836f8222018-02-20 10:31:01 -0500245 float fOpacity0 = 0,
246 fOpacity1 = 1,
247 fTimeBase = 0;
Florin Malita65fce9e2018-02-19 13:25:18 -0500248 State fState = State::kIdle;
249
250 using INHERITED = sksg::Animator;
Florin Malita76a076b2018-02-15 18:40:48 -0500251};
252
Brian Salomon343553a2018-09-05 15:41:23 -0400253SlideDir::SlideDir(const SkString& name, SkTArray<sk_sp<Slide>>&& slides, int columns)
Florin Malita76a076b2018-02-15 18:40:48 -0500254 : fSlides(std::move(slides))
255 , fColumns(columns) {
256 fName = name;
257}
258
Florin Malita65fce9e2018-02-19 13:25:18 -0500259static sk_sp<sksg::RenderNode> MakeLabel(const SkString& txt,
260 const SkPoint& pos,
261 const SkMatrix& dstXform) {
262 const auto size = kLabelSize / std::sqrt(dstXform.getScaleX() * dstXform.getScaleY());
Florin Malita76a076b2018-02-15 18:40:48 -0500263 auto text = sksg::Text::Make(nullptr, txt);
Florin Malitaf7d6ac12018-11-21 16:03:58 -0500264 text->setEdging(SkFont::Edging::kAntiAlias);
Florin Malita65fce9e2018-02-19 13:25:18 -0500265 text->setSize(size);
Mike Reed3a42ec02018-10-30 12:53:21 -0400266 text->setAlign(SkTextUtils::kCenter_Align);
Florin Malita65fce9e2018-02-19 13:25:18 -0500267 text->setPosition(pos + SkPoint::Make(0, size));
Florin Malita76a076b2018-02-15 18:40:48 -0500268
269 return sksg::Draw::Make(std::move(text), sksg::Color::Make(SK_ColorBLACK));
270}
271
272void SlideDir::load(SkScalar winWidth, SkScalar winHeight) {
273 // Build a global scene using transformed animation fragments:
274 //
275 // [Group(root)]
276 // [Transform]
277 // [Group]
278 // [AnimationWrapper]
279 // [Draw]
280 // [Text]
281 // [Color]
282 // [Transform]
283 // [Group]
284 // [AnimationWrapper]
285 // [Draw]
286 // [Text]
287 // [Color]
288 // ...
289 //
290
Florin Malita65fce9e2018-02-19 13:25:18 -0500291 fWinSize = SkSize::Make(winWidth, winHeight);
292 const auto cellWidth = winWidth / fColumns;
293 fCellSize = SkSize::Make(cellWidth, cellWidth / kAspectRatio);
Florin Malita76a076b2018-02-15 18:40:48 -0500294
295 sksg::AnimatorList sceneAnimators;
Florin Malita65fce9e2018-02-19 13:25:18 -0500296 fRoot = sksg::Group::Make();
Florin Malita76a076b2018-02-15 18:40:48 -0500297
298 for (int i = 0; i < fSlides.count(); ++i) {
299 const auto& slide = fSlides[i];
300 slide->load(winWidth, winHeight);
301
302 const auto slideSize = slide->getDimensions();
Florin Malita65fce9e2018-02-19 13:25:18 -0500303 const auto cell = SkRect::MakeXYWH(fCellSize.width() * (i % fColumns),
304 fCellSize.height() * (i / fColumns),
305 fCellSize.width(),
306 fCellSize.height()),
Florin Malita76a076b2018-02-15 18:40:48 -0500307 slideRect = cell.makeInset(kPadding.width(), kPadding.height());
308
Florin Malita760a0522019-01-10 15:24:15 -0500309 auto slideMatrix = sksg::Matrix<SkMatrix>::Make(SlideMatrix(slide, slideRect));
Florin Malita65fce9e2018-02-19 13:25:18 -0500310 auto adapter = sk_make_sp<SlideAdapter>(slide);
311 auto slideGrp = sksg::Group::Make();
312 slideGrp->addChild(sksg::Draw::Make(sksg::Rect::Make(SkRect::MakeIWH(slideSize.width(),
313 slideSize.height())),
314 sksg::Color::Make(0xfff0f0f0)));
315 slideGrp->addChild(adapter);
316 slideGrp->addChild(MakeLabel(slide->getName(),
317 SkPoint::Make(slideSize.width() / 2, slideSize.height()),
Florin Malita919e2092019-01-09 15:37:57 -0500318 slideMatrix->getMatrix()));
319 auto slideRoot = sksg::TransformEffect::Make(std::move(slideGrp), slideMatrix);
Florin Malita76a076b2018-02-15 18:40:48 -0500320
321 sceneAnimators.push_back(adapter->makeForwardingAnimator());
Florin Malita76a076b2018-02-15 18:40:48 -0500322
Florin Malita919e2092019-01-09 15:37:57 -0500323 fRoot->addChild(slideRoot);
324 fRecs.push_back({ slide, slideRoot, slideMatrix, slideRect });
Florin Malita76a076b2018-02-15 18:40:48 -0500325 }
326
Florin Malita65fce9e2018-02-19 13:25:18 -0500327 fScene = sksg::Scene::Make(fRoot, std::move(sceneAnimators));
328
329 const auto focusRect = SkRect::MakeSize(fWinSize).makeInset(kFocusInset.width(),
330 kFocusInset.height());
Mike Kleinf46d5ca2019-12-11 10:45:01 -0500331 fFocusController = std::make_unique<FocusController>(this, focusRect);
Florin Malita76a076b2018-02-15 18:40:48 -0500332}
333
334void SlideDir::unload() {
335 for (const auto& slide : fSlides) {
336 slide->unload();
337 }
338
339 fRecs.reset();
340 fScene.reset();
Florin Malita65fce9e2018-02-19 13:25:18 -0500341 fFocusController.reset();
342 fRoot.reset();
Florin Malita76a076b2018-02-15 18:40:48 -0500343 fTimeBase = 0;
344}
345
346SkISize SlideDir::getDimensions() const {
Florin Malita60d3bfc2018-02-20 16:49:20 -0500347 return SkSize::Make(fWinSize.width(),
348 fCellSize.height() * (1 + (fSlides.count() - 1) / fColumns)).toCeil();
Florin Malita76a076b2018-02-15 18:40:48 -0500349}
350
351void SlideDir::draw(SkCanvas* canvas) {
352 fScene->render(canvas);
353}
354
Hal Canary41248072019-07-11 16:32:53 -0400355bool SlideDir::animate(double nanos) {
356 SkMSec msec = TimeUtils::NanosToMSec(nanos);
Florin Malita76a076b2018-02-15 18:40:48 -0500357 if (fTimeBase == 0) {
358 // Reset the animation time.
Hal Canary41248072019-07-11 16:32:53 -0400359 fTimeBase = msec;
Florin Malita76a076b2018-02-15 18:40:48 -0500360 }
Florin Malita65fce9e2018-02-19 13:25:18 -0500361
Hal Canary41248072019-07-11 16:32:53 -0400362 const auto t = msec - fTimeBase;
Florin Malita65fce9e2018-02-19 13:25:18 -0500363 fScene->animate(t);
364 fFocusController->tick(t);
Florin Malita76a076b2018-02-15 18:40:48 -0500365
366 return true;
367}
368
369bool SlideDir::onChar(SkUnichar c) {
Florin Malita65fce9e2018-02-19 13:25:18 -0500370 if (fFocusController->hasFocus()) {
371 if (c == kUnfocusKey) {
372 fFocusController->startUnfocus();
373 return true;
374 }
375 return fFocusController->onChar(c);
376 }
377
Florin Malita76a076b2018-02-15 18:40:48 -0500378 return false;
379}
380
Hal Canaryb1f411a2019-08-29 10:39:22 -0400381bool SlideDir::onMouse(SkScalar x, SkScalar y, skui::InputState state,
382 skui::ModifierKey modifiers) {
383 modifiers &= ~skui::ModifierKey::kFirstPress;
384 if (state == skui::InputState::kMove || skstd::Any(modifiers))
Florin Malita65fce9e2018-02-19 13:25:18 -0500385 return false;
386
387 if (fFocusController->hasFocus()) {
388 return fFocusController->onMouse(x, y, state, modifiers);
389 }
390
391 const auto* cell = this->findCell(x, y);
392 if (!cell)
393 return false;
394
395 static constexpr SkScalar kClickMoveTolerance = 4;
396
397 switch (state) {
Hal Canaryb1f411a2019-08-29 10:39:22 -0400398 case skui::InputState::kDown:
Florin Malita65fce9e2018-02-19 13:25:18 -0500399 fTrackingCell = cell;
400 fTrackingPos = SkPoint::Make(x, y);
401 break;
Hal Canaryb1f411a2019-08-29 10:39:22 -0400402 case skui::InputState::kUp:
Florin Malita65fce9e2018-02-19 13:25:18 -0500403 if (cell == fTrackingCell &&
404 SkPoint::Distance(fTrackingPos, SkPoint::Make(x, y)) < kClickMoveTolerance) {
Florin Malita65fce9e2018-02-19 13:25:18 -0500405 fFocusController->startFocus(cell);
406 }
407 break;
408 default:
409 break;
410 }
411
Florin Malita76a076b2018-02-15 18:40:48 -0500412 return false;
413}
Florin Malita65fce9e2018-02-19 13:25:18 -0500414
415const SlideDir::Rec* SlideDir::findCell(float x, float y) const {
416 // TODO: use SG hit testing instead of layout info?
417 const auto size = this->getDimensions();
418 if (x < 0 || y < 0 || x >= size.width() || y >= size.height()) {
419 return nullptr;
420 }
421
422 const int col = static_cast<int>(x / fCellSize.width()),
423 row = static_cast<int>(y / fCellSize.height()),
424 idx = row * fColumns + col;
425
Florin Malita60d3bfc2018-02-20 16:49:20 -0500426 return idx < fRecs.count() ? &fRecs[idx] : nullptr;
Florin Malita65fce9e2018-02-19 13:25:18 -0500427}