blob: a6b3b141ec854820b1951d6ce17628c7ba46ac71 [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
8#include "SlideDir.h"
9
10#include "SkAnimTimer.h"
11#include "SkCanvas.h"
Florin Malita836f8222018-02-20 10:31:01 -050012#include "SkCubicMap.h"
Florin Malita76a076b2018-02-15 18:40:48 -050013#include "SkMakeUnique.h"
14#include "SkSGColor.h"
15#include "SkSGDraw.h"
16#include "SkSGGroup.h"
Florin Malita97b3d2b2018-02-20 11:26:15 -050017#include "SkSGPlane.h"
Florin Malita65fce9e2018-02-19 13:25:18 -050018#include "SkSGRect.h"
Florin Malita76a076b2018-02-15 18:40:48 -050019#include "SkSGRenderNode.h"
20#include "SkSGScene.h"
21#include "SkSGText.h"
22#include "SkSGTransform.h"
23#include "SkTypeface.h"
24
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 Malita76a076b2018-02-15 18:40:48 -050028namespace {
29
Florin Malita65fce9e2018-02-19 13:25:18 -050030static constexpr float kAspectRatio = 1.5f;
31static constexpr float kLabelSize = 12.0f;
32static constexpr SkSize kPadding = { 12.0f , 24.0f };
Florin Malita836f8222018-02-20 10:31:01 -050033
34static constexpr float kFocusDuration = 500;
35static constexpr SkSize kFocusInset = { 100.0f, 100.0f };
36static constexpr SkPoint kFocusCtrl0 = { 0.3f, 1.0f };
37static constexpr SkPoint kFocusCtrl1 = { 0.0f, 1.0f };
38static constexpr SkColor kFocusShade = 0xa0000000;
Florin Malita65fce9e2018-02-19 13:25:18 -050039
40// TODO: better unfocus binding?
41static constexpr SkUnichar kUnfocusKey = ' ';
Florin Malita76a076b2018-02-15 18:40:48 -050042
43class SlideAdapter final : public sksg::RenderNode {
44public:
45 explicit SlideAdapter(sk_sp<Slide> slide)
46 : fSlide(std::move(slide)) {
47 SkASSERT(fSlide);
48 }
49
50 std::unique_ptr<sksg::Animator> makeForwardingAnimator() {
51 // Trivial sksg::Animator -> skottie::Animation tick adapter
52 class ForwardingAnimator final : public sksg::Animator {
53 public:
54 explicit ForwardingAnimator(sk_sp<SlideAdapter> adapter)
55 : fAdapter(std::move(adapter)) {}
56
57 protected:
58 void onTick(float t) override {
59 fAdapter->tick(SkScalarRoundToInt(t));
60 }
61
62 private:
63 sk_sp<SlideAdapter> fAdapter;
64 };
65
66 return skstd::make_unique<ForwardingAnimator>(sk_ref_sp(this));
67 }
68
69protected:
70 SkRect onRevalidate(sksg::InvalidationController* ic, const SkMatrix& ctm) override {
71 const auto isize = fSlide->getDimensions();
72 return SkRect::MakeIWH(isize.width(), isize.height());
73 }
74
Florin Malitac0132ff2018-08-09 07:40:01 -040075 void onRender(SkCanvas* canvas, const RenderContext* ctx) const override {
Florin Malita6127c4c2018-04-05 15:15:59 -040076 SkAutoCanvasRestore acr(canvas, true);
77 canvas->clipRect(SkRect::Make(fSlide->getDimensions()), true);
Florin Malitac0132ff2018-08-09 07:40:01 -040078
79 // TODO: commit the context?
Florin Malita76a076b2018-02-15 18:40:48 -050080 fSlide->draw(canvas);
81 }
82
Florin Malitaeb46bd82019-02-12 09:33:21 -050083 const RenderNode* onNodeAt(const SkPoint&) const override { return nullptr; }
84
Florin Malita76a076b2018-02-15 18:40:48 -050085private:
86 void tick(SkMSec t) {
Brian Osman707d2022019-01-10 11:27:34 -050087 fSlide->animate(SkAnimTimer(t * 1e6));
Florin Malita76a076b2018-02-15 18:40:48 -050088 this->invalidate();
89 }
90
91 const sk_sp<Slide> fSlide;
92
93 using INHERITED = sksg::RenderNode;
94};
95
Florin Malita65fce9e2018-02-19 13:25:18 -050096SkMatrix SlideMatrix(const sk_sp<Slide>& slide, const SkRect& dst) {
97 const auto slideSize = slide->getDimensions();
98 return SkMatrix::MakeRectToRect(SkRect::MakeIWH(slideSize.width(), slideSize.height()),
99 dst,
100 SkMatrix::kCenter_ScaleToFit);
101}
102
Florin Malita76a076b2018-02-15 18:40:48 -0500103} // namespace
104
105struct SlideDir::Rec {
Florin Malita760a0522019-01-10 15:24:15 -0500106 sk_sp<Slide> fSlide;
107 sk_sp<sksg::RenderNode> fSlideRoot;
108 sk_sp<sksg::Matrix<SkMatrix>> fMatrix;
109 SkRect fRect;
Florin Malita65fce9e2018-02-19 13:25:18 -0500110};
111
112class SlideDir::FocusController final : public sksg::Animator {
113public:
114 FocusController(const SlideDir* dir, const SkRect& focusRect)
115 : fDir(dir)
116 , fRect(focusRect)
117 , fTarget(nullptr)
Florin Malita836f8222018-02-20 10:31:01 -0500118 , fState(State::kIdle) {
119 fMap.setPts(kFocusCtrl1, kFocusCtrl0);
120
121 fShadePaint = sksg::Color::Make(kFocusShade);
Florin Malita97b3d2b2018-02-20 11:26:15 -0500122 fShade = sksg::Draw::Make(sksg::Plane::Make(), fShadePaint);
Florin Malita836f8222018-02-20 10:31:01 -0500123 }
Florin Malita65fce9e2018-02-19 13:25:18 -0500124
125 bool hasFocus() const { return fState == State::kFocused; }
126
127 void startFocus(const Rec* target) {
Florin Malita836f8222018-02-20 10:31:01 -0500128 if (fState != State::kIdle)
129 return;
130
Florin Malita65fce9e2018-02-19 13:25:18 -0500131 fTarget = target;
Florin Malita836f8222018-02-20 10:31:01 -0500132
133 // Move the shade & slide to front.
Florin Malita919e2092019-01-09 15:37:57 -0500134 fDir->fRoot->removeChild(fTarget->fSlideRoot);
Florin Malita836f8222018-02-20 10:31:01 -0500135 fDir->fRoot->addChild(fShade);
Florin Malita919e2092019-01-09 15:37:57 -0500136 fDir->fRoot->addChild(fTarget->fSlideRoot);
Florin Malita836f8222018-02-20 10:31:01 -0500137
Florin Malita65fce9e2018-02-19 13:25:18 -0500138 fM0 = SlideMatrix(fTarget->fSlide, fTarget->fRect);
139 fM1 = SlideMatrix(fTarget->fSlide, fRect);
140
Florin Malita836f8222018-02-20 10:31:01 -0500141 fOpacity0 = 0;
142 fOpacity1 = 1;
143
Florin Malita65fce9e2018-02-19 13:25:18 -0500144 fTimeBase = 0;
145 fState = State::kFocusing;
Florin Malita836f8222018-02-20 10:31:01 -0500146
147 // Push initial state to the scene graph.
148 this->onTick(fTimeBase);
Florin Malita65fce9e2018-02-19 13:25:18 -0500149 }
150
151 void startUnfocus() {
152 SkASSERT(fTarget);
153
Ben Wagnerf08d1d02018-06-18 15:11:00 -0400154 using std::swap;
155 swap(fM0, fM1);
156 swap(fOpacity0, fOpacity1);
Florin Malita65fce9e2018-02-19 13:25:18 -0500157
158 fTimeBase = 0;
159 fState = State::kUnfocusing;
160 }
161
162 bool onMouse(SkScalar x, SkScalar y, sk_app::Window::InputState state, uint32_t modifiers) {
163 SkASSERT(fTarget);
164
Florin Malitaeb420452018-02-20 11:44:43 -0500165 if (!fRect.contains(x, y)) {
Florin Malita836f8222018-02-20 10:31:01 -0500166 this->startUnfocus();
167 return true;
168 }
169
Florin Malita65fce9e2018-02-19 13:25:18 -0500170 // Map coords to slide space.
171 const auto xform = SkMatrix::MakeRectToRect(fRect,
172 SkRect::MakeSize(fDir->fWinSize),
173 SkMatrix::kCenter_ScaleToFit);
174 const auto pt = xform.mapXY(x, y);
175
176 return fTarget->fSlide->onMouse(pt.x(), pt.y(), state, modifiers);
177 }
178
179 bool onChar(SkUnichar c) {
180 SkASSERT(fTarget);
181
182 return fTarget->fSlide->onChar(c);
183 }
184
185protected:
186 void onTick(float t) {
187 if (!this->isAnimating())
188 return;
189
190 if (!fTimeBase) {
191 fTimeBase = t;
192 }
193
Florin Malita836f8222018-02-20 10:31:01 -0500194 const auto rel_t = (t - fTimeBase) / kFocusDuration,
195 map_t = SkTPin(fMap.computeYFromX(rel_t), 0.0f, 1.0f);
Florin Malita65fce9e2018-02-19 13:25:18 -0500196
197 SkMatrix m;
198 for (int i = 0; i < 9; ++i) {
Florin Malita836f8222018-02-20 10:31:01 -0500199 m[i] = fM0[i] + map_t * (fM1[i] - fM0[i]);
Florin Malita65fce9e2018-02-19 13:25:18 -0500200 }
201
202 SkASSERT(fTarget);
Florin Malita919e2092019-01-09 15:37:57 -0500203 fTarget->fMatrix->setMatrix(m);
Florin Malita65fce9e2018-02-19 13:25:18 -0500204
Florin Malita836f8222018-02-20 10:31:01 -0500205 const auto shadeOpacity = fOpacity0 + map_t * (fOpacity1 - fOpacity0);
206 fShadePaint->setOpacity(shadeOpacity);
207
Florin Malita65fce9e2018-02-19 13:25:18 -0500208 if (rel_t < 1)
209 return;
210
211 switch (fState) {
212 case State::kFocusing:
213 fState = State::kFocused;
214 break;
215 case State::kUnfocusing:
216 fState = State::kIdle;
Florin Malita836f8222018-02-20 10:31:01 -0500217 fDir->fRoot->removeChild(fShade);
Florin Malita65fce9e2018-02-19 13:25:18 -0500218 break;
219
220 case State::kIdle:
221 case State::kFocused:
222 SkASSERT(false);
223 break;
224 }
225 }
226
227private:
228 enum class State {
229 kIdle,
230 kFocusing,
231 kUnfocusing,
232 kFocused,
233 };
234
235 bool isAnimating() const { return fState == State::kFocusing || fState == State::kUnfocusing; }
236
Florin Malita836f8222018-02-20 10:31:01 -0500237 const SlideDir* fDir;
238 const SkRect fRect;
239 const Rec* fTarget;
240
241 SkCubicMap fMap;
242 sk_sp<sksg::RenderNode> fShade;
243 sk_sp<sksg::PaintNode> fShadePaint;
Florin Malita65fce9e2018-02-19 13:25:18 -0500244
245 SkMatrix fM0 = SkMatrix::I(),
246 fM1 = SkMatrix::I();
Florin Malita836f8222018-02-20 10:31:01 -0500247 float fOpacity0 = 0,
248 fOpacity1 = 1,
249 fTimeBase = 0;
Florin Malita65fce9e2018-02-19 13:25:18 -0500250 State fState = State::kIdle;
251
252 using INHERITED = sksg::Animator;
Florin Malita76a076b2018-02-15 18:40:48 -0500253};
254
Brian Salomon343553a2018-09-05 15:41:23 -0400255SlideDir::SlideDir(const SkString& name, SkTArray<sk_sp<Slide>>&& slides, int columns)
Florin Malita76a076b2018-02-15 18:40:48 -0500256 : fSlides(std::move(slides))
257 , fColumns(columns) {
258 fName = name;
259}
260
Florin Malita65fce9e2018-02-19 13:25:18 -0500261static sk_sp<sksg::RenderNode> MakeLabel(const SkString& txt,
262 const SkPoint& pos,
263 const SkMatrix& dstXform) {
264 const auto size = kLabelSize / std::sqrt(dstXform.getScaleX() * dstXform.getScaleY());
Florin Malita76a076b2018-02-15 18:40:48 -0500265 auto text = sksg::Text::Make(nullptr, txt);
Florin Malitaf7d6ac12018-11-21 16:03:58 -0500266 text->setEdging(SkFont::Edging::kAntiAlias);
Florin Malita65fce9e2018-02-19 13:25:18 -0500267 text->setSize(size);
Mike Reed3a42ec02018-10-30 12:53:21 -0400268 text->setAlign(SkTextUtils::kCenter_Align);
Florin Malita65fce9e2018-02-19 13:25:18 -0500269 text->setPosition(pos + SkPoint::Make(0, size));
Florin Malita76a076b2018-02-15 18:40:48 -0500270
271 return sksg::Draw::Make(std::move(text), sksg::Color::Make(SK_ColorBLACK));
272}
273
274void SlideDir::load(SkScalar winWidth, SkScalar winHeight) {
275 // Build a global scene using transformed animation fragments:
276 //
277 // [Group(root)]
278 // [Transform]
279 // [Group]
280 // [AnimationWrapper]
281 // [Draw]
282 // [Text]
283 // [Color]
284 // [Transform]
285 // [Group]
286 // [AnimationWrapper]
287 // [Draw]
288 // [Text]
289 // [Color]
290 // ...
291 //
292
Florin Malita65fce9e2018-02-19 13:25:18 -0500293 fWinSize = SkSize::Make(winWidth, winHeight);
294 const auto cellWidth = winWidth / fColumns;
295 fCellSize = SkSize::Make(cellWidth, cellWidth / kAspectRatio);
Florin Malita76a076b2018-02-15 18:40:48 -0500296
297 sksg::AnimatorList sceneAnimators;
Florin Malita65fce9e2018-02-19 13:25:18 -0500298 fRoot = sksg::Group::Make();
Florin Malita76a076b2018-02-15 18:40:48 -0500299
300 for (int i = 0; i < fSlides.count(); ++i) {
301 const auto& slide = fSlides[i];
302 slide->load(winWidth, winHeight);
303
304 const auto slideSize = slide->getDimensions();
Florin Malita65fce9e2018-02-19 13:25:18 -0500305 const auto cell = SkRect::MakeXYWH(fCellSize.width() * (i % fColumns),
306 fCellSize.height() * (i / fColumns),
307 fCellSize.width(),
308 fCellSize.height()),
Florin Malita76a076b2018-02-15 18:40:48 -0500309 slideRect = cell.makeInset(kPadding.width(), kPadding.height());
310
Florin Malita760a0522019-01-10 15:24:15 -0500311 auto slideMatrix = sksg::Matrix<SkMatrix>::Make(SlideMatrix(slide, slideRect));
Florin Malita65fce9e2018-02-19 13:25:18 -0500312 auto adapter = sk_make_sp<SlideAdapter>(slide);
313 auto slideGrp = sksg::Group::Make();
314 slideGrp->addChild(sksg::Draw::Make(sksg::Rect::Make(SkRect::MakeIWH(slideSize.width(),
315 slideSize.height())),
316 sksg::Color::Make(0xfff0f0f0)));
317 slideGrp->addChild(adapter);
318 slideGrp->addChild(MakeLabel(slide->getName(),
319 SkPoint::Make(slideSize.width() / 2, slideSize.height()),
Florin Malita919e2092019-01-09 15:37:57 -0500320 slideMatrix->getMatrix()));
321 auto slideRoot = sksg::TransformEffect::Make(std::move(slideGrp), slideMatrix);
Florin Malita76a076b2018-02-15 18:40:48 -0500322
323 sceneAnimators.push_back(adapter->makeForwardingAnimator());
Florin Malita76a076b2018-02-15 18:40:48 -0500324
Florin Malita919e2092019-01-09 15:37:57 -0500325 fRoot->addChild(slideRoot);
326 fRecs.push_back({ slide, slideRoot, slideMatrix, slideRect });
Florin Malita76a076b2018-02-15 18:40:48 -0500327 }
328
Florin Malita65fce9e2018-02-19 13:25:18 -0500329 fScene = sksg::Scene::Make(fRoot, std::move(sceneAnimators));
330
331 const auto focusRect = SkRect::MakeSize(fWinSize).makeInset(kFocusInset.width(),
332 kFocusInset.height());
333 fFocusController = skstd::make_unique<FocusController>(this, focusRect);
Florin Malita76a076b2018-02-15 18:40:48 -0500334}
335
336void SlideDir::unload() {
337 for (const auto& slide : fSlides) {
338 slide->unload();
339 }
340
341 fRecs.reset();
342 fScene.reset();
Florin Malita65fce9e2018-02-19 13:25:18 -0500343 fFocusController.reset();
344 fRoot.reset();
Florin Malita76a076b2018-02-15 18:40:48 -0500345 fTimeBase = 0;
346}
347
348SkISize SlideDir::getDimensions() const {
Florin Malita60d3bfc2018-02-20 16:49:20 -0500349 return SkSize::Make(fWinSize.width(),
350 fCellSize.height() * (1 + (fSlides.count() - 1) / fColumns)).toCeil();
Florin Malita76a076b2018-02-15 18:40:48 -0500351}
352
353void SlideDir::draw(SkCanvas* canvas) {
354 fScene->render(canvas);
355}
356
357bool SlideDir::animate(const SkAnimTimer& timer) {
358 if (fTimeBase == 0) {
359 // Reset the animation time.
360 fTimeBase = timer.msec();
361 }
Florin Malita65fce9e2018-02-19 13:25:18 -0500362
363 const auto t = timer.msec() - fTimeBase;
364 fScene->animate(t);
365 fFocusController->tick(t);
Florin Malita76a076b2018-02-15 18:40:48 -0500366
367 return true;
368}
369
370bool SlideDir::onChar(SkUnichar c) {
Florin Malita65fce9e2018-02-19 13:25:18 -0500371 if (fFocusController->hasFocus()) {
372 if (c == kUnfocusKey) {
373 fFocusController->startUnfocus();
374 return true;
375 }
376 return fFocusController->onChar(c);
377 }
378
Florin Malita76a076b2018-02-15 18:40:48 -0500379 return false;
380}
381
Florin Malita65fce9e2018-02-19 13:25:18 -0500382bool SlideDir::onMouse(SkScalar x, SkScalar y, sk_app::Window::InputState state,
383 uint32_t modifiers) {
384 if (state == sk_app::Window::kMove_InputState || modifiers)
385 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) {
398 case sk_app::Window::kDown_InputState:
399 fTrackingCell = cell;
400 fTrackingPos = SkPoint::Make(x, y);
401 break;
402 case sk_app::Window::kUp_InputState:
403 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}