blob: 1da6df4678b612b8866b4267ef5fbc4f3ca82558 [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>
26
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
49 std::unique_ptr<sksg::Animator> makeForwardingAnimator() {
50 // 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
65 return skstd::make_unique<ForwardingAnimator>(sk_ref_sp(this));
66 }
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
74 void onRender(SkCanvas* canvas) const override {
Florin Malita6127c4c2018-04-05 15:15:59 -040075 SkAutoCanvasRestore acr(canvas, true);
76 canvas->clipRect(SkRect::Make(fSlide->getDimensions()), true);
Florin Malita76a076b2018-02-15 18:40:48 -050077 fSlide->draw(canvas);
78 }
79
80private:
81 void tick(SkMSec t) {
82 fSlide->animate(SkAnimTimer(0, t * 1e6, SkAnimTimer::kRunning_State));
83 this->invalidate();
84 }
85
86 const sk_sp<Slide> fSlide;
87
88 using INHERITED = sksg::RenderNode;
89};
90
Florin Malita65fce9e2018-02-19 13:25:18 -050091SkMatrix SlideMatrix(const sk_sp<Slide>& slide, const SkRect& dst) {
92 const auto slideSize = slide->getDimensions();
93 return SkMatrix::MakeRectToRect(SkRect::MakeIWH(slideSize.width(), slideSize.height()),
94 dst,
95 SkMatrix::kCenter_ScaleToFit);
96}
97
Florin Malita76a076b2018-02-15 18:40:48 -050098} // namespace
99
100struct SlideDir::Rec {
Florin Malita65fce9e2018-02-19 13:25:18 -0500101 sk_sp<Slide> fSlide;
102 sk_sp<sksg::Transform> fTransform;
103 SkRect fRect;
104};
105
106class SlideDir::FocusController final : public sksg::Animator {
107public:
108 FocusController(const SlideDir* dir, const SkRect& focusRect)
109 : fDir(dir)
110 , fRect(focusRect)
111 , fTarget(nullptr)
Florin Malita836f8222018-02-20 10:31:01 -0500112 , fState(State::kIdle) {
113 fMap.setPts(kFocusCtrl1, kFocusCtrl0);
114
115 fShadePaint = sksg::Color::Make(kFocusShade);
Florin Malita97b3d2b2018-02-20 11:26:15 -0500116 fShade = sksg::Draw::Make(sksg::Plane::Make(), fShadePaint);
Florin Malita836f8222018-02-20 10:31:01 -0500117 }
Florin Malita65fce9e2018-02-19 13:25:18 -0500118
119 bool hasFocus() const { return fState == State::kFocused; }
120
121 void startFocus(const Rec* target) {
Florin Malita836f8222018-02-20 10:31:01 -0500122 if (fState != State::kIdle)
123 return;
124
Florin Malita65fce9e2018-02-19 13:25:18 -0500125 fTarget = target;
Florin Malita836f8222018-02-20 10:31:01 -0500126
127 // Move the shade & slide to front.
128 fDir->fRoot->removeChild(fTarget->fTransform);
129 fDir->fRoot->addChild(fShade);
130 fDir->fRoot->addChild(fTarget->fTransform);
131
Florin Malita65fce9e2018-02-19 13:25:18 -0500132 fM0 = SlideMatrix(fTarget->fSlide, fTarget->fRect);
133 fM1 = SlideMatrix(fTarget->fSlide, fRect);
134
Florin Malita836f8222018-02-20 10:31:01 -0500135 fOpacity0 = 0;
136 fOpacity1 = 1;
137
Florin Malita65fce9e2018-02-19 13:25:18 -0500138 fTimeBase = 0;
139 fState = State::kFocusing;
Florin Malita836f8222018-02-20 10:31:01 -0500140
141 // Push initial state to the scene graph.
142 this->onTick(fTimeBase);
Florin Malita65fce9e2018-02-19 13:25:18 -0500143 }
144
145 void startUnfocus() {
146 SkASSERT(fTarget);
147
Florin Malita836f8222018-02-20 10:31:01 -0500148 SkTSwap(fM0, fM1);
149 SkTSwap(fOpacity0, fOpacity1);
Florin Malita65fce9e2018-02-19 13:25:18 -0500150
151 fTimeBase = 0;
152 fState = State::kUnfocusing;
153 }
154
155 bool onMouse(SkScalar x, SkScalar y, sk_app::Window::InputState state, uint32_t modifiers) {
156 SkASSERT(fTarget);
157
Florin Malitaeb420452018-02-20 11:44:43 -0500158 if (!fRect.contains(x, y)) {
Florin Malita836f8222018-02-20 10:31:01 -0500159 this->startUnfocus();
160 return true;
161 }
162
Florin Malita65fce9e2018-02-19 13:25:18 -0500163 // Map coords to slide space.
164 const auto xform = SkMatrix::MakeRectToRect(fRect,
165 SkRect::MakeSize(fDir->fWinSize),
166 SkMatrix::kCenter_ScaleToFit);
167 const auto pt = xform.mapXY(x, y);
168
169 return fTarget->fSlide->onMouse(pt.x(), pt.y(), state, modifiers);
170 }
171
172 bool onChar(SkUnichar c) {
173 SkASSERT(fTarget);
174
175 return fTarget->fSlide->onChar(c);
176 }
177
178protected:
179 void onTick(float t) {
180 if (!this->isAnimating())
181 return;
182
183 if (!fTimeBase) {
184 fTimeBase = t;
185 }
186
Florin Malita836f8222018-02-20 10:31:01 -0500187 const auto rel_t = (t - fTimeBase) / kFocusDuration,
188 map_t = SkTPin(fMap.computeYFromX(rel_t), 0.0f, 1.0f);
Florin Malita65fce9e2018-02-19 13:25:18 -0500189
190 SkMatrix m;
191 for (int i = 0; i < 9; ++i) {
Florin Malita836f8222018-02-20 10:31:01 -0500192 m[i] = fM0[i] + map_t * (fM1[i] - fM0[i]);
Florin Malita65fce9e2018-02-19 13:25:18 -0500193 }
194
195 SkASSERT(fTarget);
196 fTarget->fTransform->getMatrix()->setMatrix(m);
197
Florin Malita836f8222018-02-20 10:31:01 -0500198 const auto shadeOpacity = fOpacity0 + map_t * (fOpacity1 - fOpacity0);
199 fShadePaint->setOpacity(shadeOpacity);
200
Florin Malita65fce9e2018-02-19 13:25:18 -0500201 if (rel_t < 1)
202 return;
203
204 switch (fState) {
205 case State::kFocusing:
206 fState = State::kFocused;
207 break;
208 case State::kUnfocusing:
209 fState = State::kIdle;
Florin Malita836f8222018-02-20 10:31:01 -0500210 fDir->fRoot->removeChild(fShade);
Florin Malita65fce9e2018-02-19 13:25:18 -0500211 break;
212
213 case State::kIdle:
214 case State::kFocused:
215 SkASSERT(false);
216 break;
217 }
218 }
219
220private:
221 enum class State {
222 kIdle,
223 kFocusing,
224 kUnfocusing,
225 kFocused,
226 };
227
228 bool isAnimating() const { return fState == State::kFocusing || fState == State::kUnfocusing; }
229
Florin Malita836f8222018-02-20 10:31:01 -0500230 const SlideDir* fDir;
231 const SkRect fRect;
232 const Rec* fTarget;
233
234 SkCubicMap fMap;
235 sk_sp<sksg::RenderNode> fShade;
236 sk_sp<sksg::PaintNode> fShadePaint;
Florin Malita65fce9e2018-02-19 13:25:18 -0500237
238 SkMatrix fM0 = SkMatrix::I(),
239 fM1 = SkMatrix::I();
Florin Malita836f8222018-02-20 10:31:01 -0500240 float fOpacity0 = 0,
241 fOpacity1 = 1,
242 fTimeBase = 0;
Florin Malita65fce9e2018-02-19 13:25:18 -0500243 State fState = State::kIdle;
244
245 using INHERITED = sksg::Animator;
Florin Malita76a076b2018-02-15 18:40:48 -0500246};
247
248SlideDir::SlideDir(const SkString& name, SkTArray<sk_sp<Slide>, true>&& slides, int columns)
249 : fSlides(std::move(slides))
250 , fColumns(columns) {
251 fName = name;
252}
253
Florin Malita65fce9e2018-02-19 13:25:18 -0500254static sk_sp<sksg::RenderNode> MakeLabel(const SkString& txt,
255 const SkPoint& pos,
256 const SkMatrix& dstXform) {
257 const auto size = kLabelSize / std::sqrt(dstXform.getScaleX() * dstXform.getScaleY());
Florin Malita76a076b2018-02-15 18:40:48 -0500258 auto text = sksg::Text::Make(nullptr, txt);
259 text->setFlags(SkPaint::kAntiAlias_Flag);
Florin Malita65fce9e2018-02-19 13:25:18 -0500260 text->setSize(size);
Florin Malita76a076b2018-02-15 18:40:48 -0500261 text->setAlign(SkPaint::kCenter_Align);
Florin Malita65fce9e2018-02-19 13:25:18 -0500262 text->setPosition(pos + SkPoint::Make(0, size));
Florin Malita76a076b2018-02-15 18:40:48 -0500263
264 return sksg::Draw::Make(std::move(text), sksg::Color::Make(SK_ColorBLACK));
265}
266
267void SlideDir::load(SkScalar winWidth, SkScalar winHeight) {
268 // Build a global scene using transformed animation fragments:
269 //
270 // [Group(root)]
271 // [Transform]
272 // [Group]
273 // [AnimationWrapper]
274 // [Draw]
275 // [Text]
276 // [Color]
277 // [Transform]
278 // [Group]
279 // [AnimationWrapper]
280 // [Draw]
281 // [Text]
282 // [Color]
283 // ...
284 //
285
Florin Malita65fce9e2018-02-19 13:25:18 -0500286 fWinSize = SkSize::Make(winWidth, winHeight);
287 const auto cellWidth = winWidth / fColumns;
288 fCellSize = SkSize::Make(cellWidth, cellWidth / kAspectRatio);
Florin Malita76a076b2018-02-15 18:40:48 -0500289
290 sksg::AnimatorList sceneAnimators;
Florin Malita65fce9e2018-02-19 13:25:18 -0500291 fRoot = sksg::Group::Make();
Florin Malita76a076b2018-02-15 18:40:48 -0500292
293 for (int i = 0; i < fSlides.count(); ++i) {
294 const auto& slide = fSlides[i];
295 slide->load(winWidth, winHeight);
296
297 const auto slideSize = slide->getDimensions();
Florin Malita65fce9e2018-02-19 13:25:18 -0500298 const auto cell = SkRect::MakeXYWH(fCellSize.width() * (i % fColumns),
299 fCellSize.height() * (i / fColumns),
300 fCellSize.width(),
301 fCellSize.height()),
Florin Malita76a076b2018-02-15 18:40:48 -0500302 slideRect = cell.makeInset(kPadding.width(), kPadding.height());
303
Florin Malita65fce9e2018-02-19 13:25:18 -0500304 auto slideMatrix = SlideMatrix(slide, slideRect);
305 auto adapter = sk_make_sp<SlideAdapter>(slide);
306 auto slideGrp = sksg::Group::Make();
307 slideGrp->addChild(sksg::Draw::Make(sksg::Rect::Make(SkRect::MakeIWH(slideSize.width(),
308 slideSize.height())),
309 sksg::Color::Make(0xfff0f0f0)));
310 slideGrp->addChild(adapter);
311 slideGrp->addChild(MakeLabel(slide->getName(),
312 SkPoint::Make(slideSize.width() / 2, slideSize.height()),
313 slideMatrix));
314 auto slideTransform = sksg::Transform::Make(std::move(slideGrp), slideMatrix);
Florin Malita76a076b2018-02-15 18:40:48 -0500315
316 sceneAnimators.push_back(adapter->makeForwardingAnimator());
Florin Malita76a076b2018-02-15 18:40:48 -0500317
Florin Malita65fce9e2018-02-19 13:25:18 -0500318 fRoot->addChild(slideTransform);
319 fRecs.push_back({ slide, slideTransform, slideRect });
Florin Malita76a076b2018-02-15 18:40:48 -0500320 }
321
Florin Malita65fce9e2018-02-19 13:25:18 -0500322 fScene = sksg::Scene::Make(fRoot, std::move(sceneAnimators));
323
324 const auto focusRect = SkRect::MakeSize(fWinSize).makeInset(kFocusInset.width(),
325 kFocusInset.height());
326 fFocusController = skstd::make_unique<FocusController>(this, focusRect);
Florin Malita76a076b2018-02-15 18:40:48 -0500327}
328
329void SlideDir::unload() {
330 for (const auto& slide : fSlides) {
331 slide->unload();
332 }
333
334 fRecs.reset();
335 fScene.reset();
Florin Malita65fce9e2018-02-19 13:25:18 -0500336 fFocusController.reset();
337 fRoot.reset();
Florin Malita76a076b2018-02-15 18:40:48 -0500338 fTimeBase = 0;
339}
340
341SkISize SlideDir::getDimensions() const {
Florin Malita60d3bfc2018-02-20 16:49:20 -0500342 return SkSize::Make(fWinSize.width(),
343 fCellSize.height() * (1 + (fSlides.count() - 1) / fColumns)).toCeil();
Florin Malita76a076b2018-02-15 18:40:48 -0500344}
345
346void SlideDir::draw(SkCanvas* canvas) {
347 fScene->render(canvas);
348}
349
350bool SlideDir::animate(const SkAnimTimer& timer) {
351 if (fTimeBase == 0) {
352 // Reset the animation time.
353 fTimeBase = timer.msec();
354 }
Florin Malita65fce9e2018-02-19 13:25:18 -0500355
356 const auto t = timer.msec() - fTimeBase;
357 fScene->animate(t);
358 fFocusController->tick(t);
Florin Malita76a076b2018-02-15 18:40:48 -0500359
360 return true;
361}
362
363bool SlideDir::onChar(SkUnichar c) {
Florin Malita65fce9e2018-02-19 13:25:18 -0500364 if (fFocusController->hasFocus()) {
365 if (c == kUnfocusKey) {
366 fFocusController->startUnfocus();
367 return true;
368 }
369 return fFocusController->onChar(c);
370 }
371
Florin Malita76a076b2018-02-15 18:40:48 -0500372 return false;
373}
374
Florin Malita65fce9e2018-02-19 13:25:18 -0500375bool SlideDir::onMouse(SkScalar x, SkScalar y, sk_app::Window::InputState state,
376 uint32_t modifiers) {
377 if (state == sk_app::Window::kMove_InputState || modifiers)
378 return false;
379
380 if (fFocusController->hasFocus()) {
381 return fFocusController->onMouse(x, y, state, modifiers);
382 }
383
384 const auto* cell = this->findCell(x, y);
385 if (!cell)
386 return false;
387
388 static constexpr SkScalar kClickMoveTolerance = 4;
389
390 switch (state) {
391 case sk_app::Window::kDown_InputState:
392 fTrackingCell = cell;
393 fTrackingPos = SkPoint::Make(x, y);
394 break;
395 case sk_app::Window::kUp_InputState:
396 if (cell == fTrackingCell &&
397 SkPoint::Distance(fTrackingPos, SkPoint::Make(x, y)) < kClickMoveTolerance) {
Florin Malita65fce9e2018-02-19 13:25:18 -0500398 fFocusController->startFocus(cell);
399 }
400 break;
401 default:
402 break;
403 }
404
Florin Malita76a076b2018-02-15 18:40:48 -0500405 return false;
406}
Florin Malita65fce9e2018-02-19 13:25:18 -0500407
408const SlideDir::Rec* SlideDir::findCell(float x, float y) const {
409 // TODO: use SG hit testing instead of layout info?
410 const auto size = this->getDimensions();
411 if (x < 0 || y < 0 || x >= size.width() || y >= size.height()) {
412 return nullptr;
413 }
414
415 const int col = static_cast<int>(x / fCellSize.width()),
416 row = static_cast<int>(y / fCellSize.height()),
417 idx = row * fColumns + col;
418
Florin Malita60d3bfc2018-02-20 16:49:20 -0500419 return idx < fRecs.count() ? &fRecs[idx] : nullptr;
Florin Malita65fce9e2018-02-19 13:25:18 -0500420}