blob: a7fe77b2630b7babf8056c633f30b9017f58df33 [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 Malita65fce9e2018-02-19 13:25:18 -050017#include "SkSGRect.h"
Florin Malita76a076b2018-02-15 18:40:48 -050018#include "SkSGRenderNode.h"
19#include "SkSGScene.h"
20#include "SkSGText.h"
21#include "SkSGTransform.h"
22#include "SkTypeface.h"
23
Florin Malita65fce9e2018-02-19 13:25:18 -050024#include <cmath>
25
Florin Malita76a076b2018-02-15 18:40:48 -050026namespace {
27
Florin Malita65fce9e2018-02-19 13:25:18 -050028static constexpr float kAspectRatio = 1.5f;
29static constexpr float kLabelSize = 12.0f;
30static constexpr SkSize kPadding = { 12.0f , 24.0f };
Florin Malita836f8222018-02-20 10:31:01 -050031
32static constexpr float kFocusDuration = 500;
33static constexpr SkSize kFocusInset = { 100.0f, 100.0f };
34static constexpr SkPoint kFocusCtrl0 = { 0.3f, 1.0f };
35static constexpr SkPoint kFocusCtrl1 = { 0.0f, 1.0f };
36static constexpr SkColor kFocusShade = 0xa0000000;
Florin Malita65fce9e2018-02-19 13:25:18 -050037
38// TODO: better unfocus binding?
39static constexpr SkUnichar kUnfocusKey = ' ';
Florin Malita76a076b2018-02-15 18:40:48 -050040
41class SlideAdapter final : public sksg::RenderNode {
42public:
43 explicit SlideAdapter(sk_sp<Slide> slide)
44 : fSlide(std::move(slide)) {
45 SkASSERT(fSlide);
46 }
47
48 std::unique_ptr<sksg::Animator> makeForwardingAnimator() {
49 // Trivial sksg::Animator -> skottie::Animation tick adapter
50 class ForwardingAnimator final : public sksg::Animator {
51 public:
52 explicit ForwardingAnimator(sk_sp<SlideAdapter> adapter)
53 : fAdapter(std::move(adapter)) {}
54
55 protected:
56 void onTick(float t) override {
57 fAdapter->tick(SkScalarRoundToInt(t));
58 }
59
60 private:
61 sk_sp<SlideAdapter> fAdapter;
62 };
63
64 return skstd::make_unique<ForwardingAnimator>(sk_ref_sp(this));
65 }
66
67protected:
68 SkRect onRevalidate(sksg::InvalidationController* ic, const SkMatrix& ctm) override {
69 const auto isize = fSlide->getDimensions();
70 return SkRect::MakeIWH(isize.width(), isize.height());
71 }
72
73 void onRender(SkCanvas* canvas) const override {
74 fSlide->draw(canvas);
75 }
76
77private:
78 void tick(SkMSec t) {
79 fSlide->animate(SkAnimTimer(0, t * 1e6, SkAnimTimer::kRunning_State));
80 this->invalidate();
81 }
82
83 const sk_sp<Slide> fSlide;
84
85 using INHERITED = sksg::RenderNode;
86};
87
Florin Malita65fce9e2018-02-19 13:25:18 -050088SkMatrix SlideMatrix(const sk_sp<Slide>& slide, const SkRect& dst) {
89 const auto slideSize = slide->getDimensions();
90 return SkMatrix::MakeRectToRect(SkRect::MakeIWH(slideSize.width(), slideSize.height()),
91 dst,
92 SkMatrix::kCenter_ScaleToFit);
93}
94
Florin Malita76a076b2018-02-15 18:40:48 -050095} // namespace
96
97struct SlideDir::Rec {
Florin Malita65fce9e2018-02-19 13:25:18 -050098 sk_sp<Slide> fSlide;
99 sk_sp<sksg::Transform> fTransform;
100 SkRect fRect;
101};
102
103class SlideDir::FocusController final : public sksg::Animator {
104public:
105 FocusController(const SlideDir* dir, const SkRect& focusRect)
106 : fDir(dir)
107 , fRect(focusRect)
108 , fTarget(nullptr)
Florin Malita836f8222018-02-20 10:31:01 -0500109 , fState(State::kIdle) {
110 fMap.setPts(kFocusCtrl1, kFocusCtrl0);
111
112 fShadePaint = sksg::Color::Make(kFocusShade);
113 fShade = sksg::Draw::Make(sksg::Rect::Make(SkRect::MakeSize(dir->fWinSize)), fShadePaint);
114 }
Florin Malita65fce9e2018-02-19 13:25:18 -0500115
116 bool hasFocus() const { return fState == State::kFocused; }
117
118 void startFocus(const Rec* target) {
Florin Malita836f8222018-02-20 10:31:01 -0500119 if (fState != State::kIdle)
120 return;
121
Florin Malita65fce9e2018-02-19 13:25:18 -0500122 fTarget = target;
Florin Malita836f8222018-02-20 10:31:01 -0500123
124 // Move the shade & slide to front.
125 fDir->fRoot->removeChild(fTarget->fTransform);
126 fDir->fRoot->addChild(fShade);
127 fDir->fRoot->addChild(fTarget->fTransform);
128
Florin Malita65fce9e2018-02-19 13:25:18 -0500129 fM0 = SlideMatrix(fTarget->fSlide, fTarget->fRect);
130 fM1 = SlideMatrix(fTarget->fSlide, fRect);
131
Florin Malita836f8222018-02-20 10:31:01 -0500132 fOpacity0 = 0;
133 fOpacity1 = 1;
134
Florin Malita65fce9e2018-02-19 13:25:18 -0500135 fTimeBase = 0;
136 fState = State::kFocusing;
Florin Malita836f8222018-02-20 10:31:01 -0500137
138 // Push initial state to the scene graph.
139 this->onTick(fTimeBase);
Florin Malita65fce9e2018-02-19 13:25:18 -0500140 }
141
142 void startUnfocus() {
143 SkASSERT(fTarget);
144
Florin Malita836f8222018-02-20 10:31:01 -0500145 SkTSwap(fM0, fM1);
146 SkTSwap(fOpacity0, fOpacity1);
Florin Malita65fce9e2018-02-19 13:25:18 -0500147
148 fTimeBase = 0;
149 fState = State::kUnfocusing;
150 }
151
152 bool onMouse(SkScalar x, SkScalar y, sk_app::Window::InputState state, uint32_t modifiers) {
153 SkASSERT(fTarget);
154
Florin Malita836f8222018-02-20 10:31:01 -0500155 // TODO: no SkRect::contains(SkPoint) ?!
156 if (x < fRect.fLeft || x > fRect.fRight || y < fRect.fTop || y > fRect.fBottom) {
157 this->startUnfocus();
158 return true;
159 }
160
Florin Malita65fce9e2018-02-19 13:25:18 -0500161 // Map coords to slide space.
162 const auto xform = SkMatrix::MakeRectToRect(fRect,
163 SkRect::MakeSize(fDir->fWinSize),
164 SkMatrix::kCenter_ScaleToFit);
165 const auto pt = xform.mapXY(x, y);
166
167 return fTarget->fSlide->onMouse(pt.x(), pt.y(), state, modifiers);
168 }
169
170 bool onChar(SkUnichar c) {
171 SkASSERT(fTarget);
172
173 return fTarget->fSlide->onChar(c);
174 }
175
176protected:
177 void onTick(float t) {
178 if (!this->isAnimating())
179 return;
180
181 if (!fTimeBase) {
182 fTimeBase = t;
183 }
184
Florin Malita836f8222018-02-20 10:31:01 -0500185 const auto rel_t = (t - fTimeBase) / kFocusDuration,
186 map_t = SkTPin(fMap.computeYFromX(rel_t), 0.0f, 1.0f);
Florin Malita65fce9e2018-02-19 13:25:18 -0500187
188 SkMatrix m;
189 for (int i = 0; i < 9; ++i) {
Florin Malita836f8222018-02-20 10:31:01 -0500190 m[i] = fM0[i] + map_t * (fM1[i] - fM0[i]);
Florin Malita65fce9e2018-02-19 13:25:18 -0500191 }
192
193 SkASSERT(fTarget);
194 fTarget->fTransform->getMatrix()->setMatrix(m);
195
Florin Malita836f8222018-02-20 10:31:01 -0500196 const auto shadeOpacity = fOpacity0 + map_t * (fOpacity1 - fOpacity0);
197 fShadePaint->setOpacity(shadeOpacity);
198
Florin Malita65fce9e2018-02-19 13:25:18 -0500199 if (rel_t < 1)
200 return;
201
202 switch (fState) {
203 case State::kFocusing:
204 fState = State::kFocused;
205 break;
206 case State::kUnfocusing:
207 fState = State::kIdle;
Florin Malita836f8222018-02-20 10:31:01 -0500208 fDir->fRoot->removeChild(fShade);
Florin Malita65fce9e2018-02-19 13:25:18 -0500209 break;
210
211 case State::kIdle:
212 case State::kFocused:
213 SkASSERT(false);
214 break;
215 }
216 }
217
218private:
219 enum class State {
220 kIdle,
221 kFocusing,
222 kUnfocusing,
223 kFocused,
224 };
225
226 bool isAnimating() const { return fState == State::kFocusing || fState == State::kUnfocusing; }
227
Florin Malita836f8222018-02-20 10:31:01 -0500228 const SlideDir* fDir;
229 const SkRect fRect;
230 const Rec* fTarget;
231
232 SkCubicMap fMap;
233 sk_sp<sksg::RenderNode> fShade;
234 sk_sp<sksg::PaintNode> fShadePaint;
Florin Malita65fce9e2018-02-19 13:25:18 -0500235
236 SkMatrix fM0 = SkMatrix::I(),
237 fM1 = SkMatrix::I();
Florin Malita836f8222018-02-20 10:31:01 -0500238 float fOpacity0 = 0,
239 fOpacity1 = 1,
240 fTimeBase = 0;
Florin Malita65fce9e2018-02-19 13:25:18 -0500241 State fState = State::kIdle;
242
243 using INHERITED = sksg::Animator;
Florin Malita76a076b2018-02-15 18:40:48 -0500244};
245
246SlideDir::SlideDir(const SkString& name, SkTArray<sk_sp<Slide>, true>&& slides, int columns)
247 : fSlides(std::move(slides))
248 , fColumns(columns) {
249 fName = name;
250}
251
Florin Malita65fce9e2018-02-19 13:25:18 -0500252static sk_sp<sksg::RenderNode> MakeLabel(const SkString& txt,
253 const SkPoint& pos,
254 const SkMatrix& dstXform) {
255 const auto size = kLabelSize / std::sqrt(dstXform.getScaleX() * dstXform.getScaleY());
Florin Malita76a076b2018-02-15 18:40:48 -0500256 auto text = sksg::Text::Make(nullptr, txt);
257 text->setFlags(SkPaint::kAntiAlias_Flag);
Florin Malita65fce9e2018-02-19 13:25:18 -0500258 text->setSize(size);
Florin Malita76a076b2018-02-15 18:40:48 -0500259 text->setAlign(SkPaint::kCenter_Align);
Florin Malita65fce9e2018-02-19 13:25:18 -0500260 text->setPosition(pos + SkPoint::Make(0, size));
Florin Malita76a076b2018-02-15 18:40:48 -0500261
262 return sksg::Draw::Make(std::move(text), sksg::Color::Make(SK_ColorBLACK));
263}
264
265void SlideDir::load(SkScalar winWidth, SkScalar winHeight) {
266 // Build a global scene using transformed animation fragments:
267 //
268 // [Group(root)]
269 // [Transform]
270 // [Group]
271 // [AnimationWrapper]
272 // [Draw]
273 // [Text]
274 // [Color]
275 // [Transform]
276 // [Group]
277 // [AnimationWrapper]
278 // [Draw]
279 // [Text]
280 // [Color]
281 // ...
282 //
283
Florin Malita65fce9e2018-02-19 13:25:18 -0500284 fWinSize = SkSize::Make(winWidth, winHeight);
285 const auto cellWidth = winWidth / fColumns;
286 fCellSize = SkSize::Make(cellWidth, cellWidth / kAspectRatio);
Florin Malita76a076b2018-02-15 18:40:48 -0500287
288 sksg::AnimatorList sceneAnimators;
Florin Malita65fce9e2018-02-19 13:25:18 -0500289 fRoot = sksg::Group::Make();
Florin Malita76a076b2018-02-15 18:40:48 -0500290
291 for (int i = 0; i < fSlides.count(); ++i) {
292 const auto& slide = fSlides[i];
293 slide->load(winWidth, winHeight);
294
295 const auto slideSize = slide->getDimensions();
Florin Malita65fce9e2018-02-19 13:25:18 -0500296 const auto cell = SkRect::MakeXYWH(fCellSize.width() * (i % fColumns),
297 fCellSize.height() * (i / fColumns),
298 fCellSize.width(),
299 fCellSize.height()),
Florin Malita76a076b2018-02-15 18:40:48 -0500300 slideRect = cell.makeInset(kPadding.width(), kPadding.height());
301
Florin Malita65fce9e2018-02-19 13:25:18 -0500302 auto slideMatrix = SlideMatrix(slide, slideRect);
303 auto adapter = sk_make_sp<SlideAdapter>(slide);
304 auto slideGrp = sksg::Group::Make();
305 slideGrp->addChild(sksg::Draw::Make(sksg::Rect::Make(SkRect::MakeIWH(slideSize.width(),
306 slideSize.height())),
307 sksg::Color::Make(0xfff0f0f0)));
308 slideGrp->addChild(adapter);
309 slideGrp->addChild(MakeLabel(slide->getName(),
310 SkPoint::Make(slideSize.width() / 2, slideSize.height()),
311 slideMatrix));
312 auto slideTransform = sksg::Transform::Make(std::move(slideGrp), slideMatrix);
Florin Malita76a076b2018-02-15 18:40:48 -0500313
314 sceneAnimators.push_back(adapter->makeForwardingAnimator());
Florin Malita76a076b2018-02-15 18:40:48 -0500315
Florin Malita65fce9e2018-02-19 13:25:18 -0500316 fRoot->addChild(slideTransform);
317 fRecs.push_back({ slide, slideTransform, slideRect });
Florin Malita76a076b2018-02-15 18:40:48 -0500318 }
319
Florin Malita65fce9e2018-02-19 13:25:18 -0500320 fScene = sksg::Scene::Make(fRoot, std::move(sceneAnimators));
321
322 const auto focusRect = SkRect::MakeSize(fWinSize).makeInset(kFocusInset.width(),
323 kFocusInset.height());
324 fFocusController = skstd::make_unique<FocusController>(this, focusRect);
Florin Malita76a076b2018-02-15 18:40:48 -0500325}
326
327void SlideDir::unload() {
328 for (const auto& slide : fSlides) {
329 slide->unload();
330 }
331
332 fRecs.reset();
333 fScene.reset();
Florin Malita65fce9e2018-02-19 13:25:18 -0500334 fFocusController.reset();
335 fRoot.reset();
Florin Malita76a076b2018-02-15 18:40:48 -0500336 fTimeBase = 0;
337}
338
339SkISize SlideDir::getDimensions() const {
Florin Malita65fce9e2018-02-19 13:25:18 -0500340 return SkSize::Make(fWinSize.width(),
341 fCellSize.height() * (fSlides.count() / fColumns)).toCeil();
Florin Malita76a076b2018-02-15 18:40:48 -0500342}
343
344void SlideDir::draw(SkCanvas* canvas) {
345 fScene->render(canvas);
346}
347
348bool SlideDir::animate(const SkAnimTimer& timer) {
349 if (fTimeBase == 0) {
350 // Reset the animation time.
351 fTimeBase = timer.msec();
352 }
Florin Malita65fce9e2018-02-19 13:25:18 -0500353
354 const auto t = timer.msec() - fTimeBase;
355 fScene->animate(t);
356 fFocusController->tick(t);
Florin Malita76a076b2018-02-15 18:40:48 -0500357
358 return true;
359}
360
361bool SlideDir::onChar(SkUnichar c) {
Florin Malita65fce9e2018-02-19 13:25:18 -0500362 if (fFocusController->hasFocus()) {
363 if (c == kUnfocusKey) {
364 fFocusController->startUnfocus();
365 return true;
366 }
367 return fFocusController->onChar(c);
368 }
369
Florin Malita76a076b2018-02-15 18:40:48 -0500370 return false;
371}
372
Florin Malita65fce9e2018-02-19 13:25:18 -0500373bool SlideDir::onMouse(SkScalar x, SkScalar y, sk_app::Window::InputState state,
374 uint32_t modifiers) {
375 if (state == sk_app::Window::kMove_InputState || modifiers)
376 return false;
377
378 if (fFocusController->hasFocus()) {
379 return fFocusController->onMouse(x, y, state, modifiers);
380 }
381
382 const auto* cell = this->findCell(x, y);
383 if (!cell)
384 return false;
385
386 static constexpr SkScalar kClickMoveTolerance = 4;
387
388 switch (state) {
389 case sk_app::Window::kDown_InputState:
390 fTrackingCell = cell;
391 fTrackingPos = SkPoint::Make(x, y);
392 break;
393 case sk_app::Window::kUp_InputState:
394 if (cell == fTrackingCell &&
395 SkPoint::Distance(fTrackingPos, SkPoint::Make(x, y)) < kClickMoveTolerance) {
Florin Malita65fce9e2018-02-19 13:25:18 -0500396 fFocusController->startFocus(cell);
397 }
398 break;
399 default:
400 break;
401 }
402
Florin Malita76a076b2018-02-15 18:40:48 -0500403 return false;
404}
Florin Malita65fce9e2018-02-19 13:25:18 -0500405
406const SlideDir::Rec* SlideDir::findCell(float x, float y) const {
407 // TODO: use SG hit testing instead of layout info?
408 const auto size = this->getDimensions();
409 if (x < 0 || y < 0 || x >= size.width() || y >= size.height()) {
410 return nullptr;
411 }
412
413 const int col = static_cast<int>(x / fCellSize.width()),
414 row = static_cast<int>(y / fCellSize.height()),
415 idx = row * fColumns + col;
416
417 return idx <= fRecs.count() ? &fRecs[idx] : nullptr;
418}