blob: b48c987d3aa8777e907145bb34249402aca77961 [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"
12#include "SkMakeUnique.h"
13#include "SkSGColor.h"
14#include "SkSGDraw.h"
15#include "SkSGGroup.h"
Florin Malita65fce9e2018-02-19 13:25:18 -050016#include "SkSGRect.h"
Florin Malita76a076b2018-02-15 18:40:48 -050017#include "SkSGRenderNode.h"
18#include "SkSGScene.h"
19#include "SkSGText.h"
20#include "SkSGTransform.h"
21#include "SkTypeface.h"
22
Florin Malita65fce9e2018-02-19 13:25:18 -050023#include <cmath>
24
Florin Malita76a076b2018-02-15 18:40:48 -050025namespace {
26
Florin Malita65fce9e2018-02-19 13:25:18 -050027static constexpr float kAspectRatio = 1.5f;
28static constexpr float kLabelSize = 12.0f;
29static constexpr SkSize kPadding = { 12.0f , 24.0f };
30static constexpr SkSize kFocusInset = { 100.0f, 100.0f };
31static constexpr float kFocusDuration = 250;
32
33// TODO: better unfocus binding?
34static constexpr SkUnichar kUnfocusKey = ' ';
Florin Malita76a076b2018-02-15 18:40:48 -050035
36class SlideAdapter final : public sksg::RenderNode {
37public:
38 explicit SlideAdapter(sk_sp<Slide> slide)
39 : fSlide(std::move(slide)) {
40 SkASSERT(fSlide);
41 }
42
43 std::unique_ptr<sksg::Animator> makeForwardingAnimator() {
44 // Trivial sksg::Animator -> skottie::Animation tick adapter
45 class ForwardingAnimator final : public sksg::Animator {
46 public:
47 explicit ForwardingAnimator(sk_sp<SlideAdapter> adapter)
48 : fAdapter(std::move(adapter)) {}
49
50 protected:
51 void onTick(float t) override {
52 fAdapter->tick(SkScalarRoundToInt(t));
53 }
54
55 private:
56 sk_sp<SlideAdapter> fAdapter;
57 };
58
59 return skstd::make_unique<ForwardingAnimator>(sk_ref_sp(this));
60 }
61
62protected:
63 SkRect onRevalidate(sksg::InvalidationController* ic, const SkMatrix& ctm) override {
64 const auto isize = fSlide->getDimensions();
65 return SkRect::MakeIWH(isize.width(), isize.height());
66 }
67
68 void onRender(SkCanvas* canvas) const override {
69 fSlide->draw(canvas);
70 }
71
72private:
73 void tick(SkMSec t) {
74 fSlide->animate(SkAnimTimer(0, t * 1e6, SkAnimTimer::kRunning_State));
75 this->invalidate();
76 }
77
78 const sk_sp<Slide> fSlide;
79
80 using INHERITED = sksg::RenderNode;
81};
82
Florin Malita65fce9e2018-02-19 13:25:18 -050083SkMatrix SlideMatrix(const sk_sp<Slide>& slide, const SkRect& dst) {
84 const auto slideSize = slide->getDimensions();
85 return SkMatrix::MakeRectToRect(SkRect::MakeIWH(slideSize.width(), slideSize.height()),
86 dst,
87 SkMatrix::kCenter_ScaleToFit);
88}
89
Florin Malita76a076b2018-02-15 18:40:48 -050090} // namespace
91
92struct SlideDir::Rec {
Florin Malita65fce9e2018-02-19 13:25:18 -050093 sk_sp<Slide> fSlide;
94 sk_sp<sksg::Transform> fTransform;
95 SkRect fRect;
96};
97
98class SlideDir::FocusController final : public sksg::Animator {
99public:
100 FocusController(const SlideDir* dir, const SkRect& focusRect)
101 : fDir(dir)
102 , fRect(focusRect)
103 , fTarget(nullptr)
104 , fState(State::kIdle) {}
105
106 bool hasFocus() const { return fState == State::kFocused; }
107
108 void startFocus(const Rec* target) {
109 fTarget = target;
110 fM0 = SlideMatrix(fTarget->fSlide, fTarget->fRect);
111 fM1 = SlideMatrix(fTarget->fSlide, fRect);
112
113 fTimeBase = 0;
114 fState = State::kFocusing;
115 }
116
117 void startUnfocus() {
118 SkASSERT(fTarget);
119
120 SkTSwap(fM1, fM0);
121
122 fTimeBase = 0;
123 fState = State::kUnfocusing;
124 }
125
126 bool onMouse(SkScalar x, SkScalar y, sk_app::Window::InputState state, uint32_t modifiers) {
127 SkASSERT(fTarget);
128
129 // Map coords to slide space.
130 const auto xform = SkMatrix::MakeRectToRect(fRect,
131 SkRect::MakeSize(fDir->fWinSize),
132 SkMatrix::kCenter_ScaleToFit);
133 const auto pt = xform.mapXY(x, y);
134
135 return fTarget->fSlide->onMouse(pt.x(), pt.y(), state, modifiers);
136 }
137
138 bool onChar(SkUnichar c) {
139 SkASSERT(fTarget);
140
141 return fTarget->fSlide->onChar(c);
142 }
143
144protected:
145 void onTick(float t) {
146 if (!this->isAnimating())
147 return;
148
149 if (!fTimeBase) {
150 fTimeBase = t;
151 }
152
153 const auto rel_t = SkTPin((t - fTimeBase) / kFocusDuration, 0.0f, 1.0f);
154
155 SkMatrix m;
156 for (int i = 0; i < 9; ++i) {
157 m[i] = fM0[i] + rel_t * (fM1[i] - fM0[i]);
158 }
159
160 SkASSERT(fTarget);
161 fTarget->fTransform->getMatrix()->setMatrix(m);
162
163 if (rel_t < 1)
164 return;
165
166 switch (fState) {
167 case State::kFocusing:
168 fState = State::kFocused;
169 break;
170 case State::kUnfocusing:
171 fState = State::kIdle;
172 break;
173
174 case State::kIdle:
175 case State::kFocused:
176 SkASSERT(false);
177 break;
178 }
179 }
180
181private:
182 enum class State {
183 kIdle,
184 kFocusing,
185 kUnfocusing,
186 kFocused,
187 };
188
189 bool isAnimating() const { return fState == State::kFocusing || fState == State::kUnfocusing; }
190
191 const SlideDir* fDir;
192 const SkRect fRect;
193 const Rec* fTarget;
194
195 SkMatrix fM0 = SkMatrix::I(),
196 fM1 = SkMatrix::I();
197 float fTimeBase = 0;
198 State fState = State::kIdle;
199
200 using INHERITED = sksg::Animator;
Florin Malita76a076b2018-02-15 18:40:48 -0500201};
202
203SlideDir::SlideDir(const SkString& name, SkTArray<sk_sp<Slide>, true>&& slides, int columns)
204 : fSlides(std::move(slides))
205 , fColumns(columns) {
206 fName = name;
207}
208
Florin Malita65fce9e2018-02-19 13:25:18 -0500209static sk_sp<sksg::RenderNode> MakeLabel(const SkString& txt,
210 const SkPoint& pos,
211 const SkMatrix& dstXform) {
212 const auto size = kLabelSize / std::sqrt(dstXform.getScaleX() * dstXform.getScaleY());
Florin Malita76a076b2018-02-15 18:40:48 -0500213 auto text = sksg::Text::Make(nullptr, txt);
214 text->setFlags(SkPaint::kAntiAlias_Flag);
Florin Malita65fce9e2018-02-19 13:25:18 -0500215 text->setSize(size);
Florin Malita76a076b2018-02-15 18:40:48 -0500216 text->setAlign(SkPaint::kCenter_Align);
Florin Malita65fce9e2018-02-19 13:25:18 -0500217 text->setPosition(pos + SkPoint::Make(0, size));
Florin Malita76a076b2018-02-15 18:40:48 -0500218
219 return sksg::Draw::Make(std::move(text), sksg::Color::Make(SK_ColorBLACK));
220}
221
222void SlideDir::load(SkScalar winWidth, SkScalar winHeight) {
223 // Build a global scene using transformed animation fragments:
224 //
225 // [Group(root)]
226 // [Transform]
227 // [Group]
228 // [AnimationWrapper]
229 // [Draw]
230 // [Text]
231 // [Color]
232 // [Transform]
233 // [Group]
234 // [AnimationWrapper]
235 // [Draw]
236 // [Text]
237 // [Color]
238 // ...
239 //
240
Florin Malita65fce9e2018-02-19 13:25:18 -0500241 fWinSize = SkSize::Make(winWidth, winHeight);
242 const auto cellWidth = winWidth / fColumns;
243 fCellSize = SkSize::Make(cellWidth, cellWidth / kAspectRatio);
Florin Malita76a076b2018-02-15 18:40:48 -0500244
245 sksg::AnimatorList sceneAnimators;
Florin Malita65fce9e2018-02-19 13:25:18 -0500246 fRoot = sksg::Group::Make();
Florin Malita76a076b2018-02-15 18:40:48 -0500247
248 for (int i = 0; i < fSlides.count(); ++i) {
249 const auto& slide = fSlides[i];
250 slide->load(winWidth, winHeight);
251
252 const auto slideSize = slide->getDimensions();
Florin Malita65fce9e2018-02-19 13:25:18 -0500253 const auto cell = SkRect::MakeXYWH(fCellSize.width() * (i % fColumns),
254 fCellSize.height() * (i / fColumns),
255 fCellSize.width(),
256 fCellSize.height()),
Florin Malita76a076b2018-02-15 18:40:48 -0500257 slideRect = cell.makeInset(kPadding.width(), kPadding.height());
258
Florin Malita65fce9e2018-02-19 13:25:18 -0500259 auto slideMatrix = SlideMatrix(slide, slideRect);
260 auto adapter = sk_make_sp<SlideAdapter>(slide);
261 auto slideGrp = sksg::Group::Make();
262 slideGrp->addChild(sksg::Draw::Make(sksg::Rect::Make(SkRect::MakeIWH(slideSize.width(),
263 slideSize.height())),
264 sksg::Color::Make(0xfff0f0f0)));
265 slideGrp->addChild(adapter);
266 slideGrp->addChild(MakeLabel(slide->getName(),
267 SkPoint::Make(slideSize.width() / 2, slideSize.height()),
268 slideMatrix));
269 auto slideTransform = sksg::Transform::Make(std::move(slideGrp), slideMatrix);
Florin Malita76a076b2018-02-15 18:40:48 -0500270
271 sceneAnimators.push_back(adapter->makeForwardingAnimator());
Florin Malita76a076b2018-02-15 18:40:48 -0500272
Florin Malita65fce9e2018-02-19 13:25:18 -0500273 fRoot->addChild(slideTransform);
274 fRecs.push_back({ slide, slideTransform, slideRect });
Florin Malita76a076b2018-02-15 18:40:48 -0500275 }
276
Florin Malita65fce9e2018-02-19 13:25:18 -0500277 fScene = sksg::Scene::Make(fRoot, std::move(sceneAnimators));
278
279 const auto focusRect = SkRect::MakeSize(fWinSize).makeInset(kFocusInset.width(),
280 kFocusInset.height());
281 fFocusController = skstd::make_unique<FocusController>(this, focusRect);
Florin Malita76a076b2018-02-15 18:40:48 -0500282}
283
284void SlideDir::unload() {
285 for (const auto& slide : fSlides) {
286 slide->unload();
287 }
288
289 fRecs.reset();
290 fScene.reset();
Florin Malita65fce9e2018-02-19 13:25:18 -0500291 fFocusController.reset();
292 fRoot.reset();
Florin Malita76a076b2018-02-15 18:40:48 -0500293 fTimeBase = 0;
294}
295
296SkISize SlideDir::getDimensions() const {
Florin Malita65fce9e2018-02-19 13:25:18 -0500297 return SkSize::Make(fWinSize.width(),
298 fCellSize.height() * (fSlides.count() / fColumns)).toCeil();
Florin Malita76a076b2018-02-15 18:40:48 -0500299}
300
301void SlideDir::draw(SkCanvas* canvas) {
302 fScene->render(canvas);
303}
304
305bool SlideDir::animate(const SkAnimTimer& timer) {
306 if (fTimeBase == 0) {
307 // Reset the animation time.
308 fTimeBase = timer.msec();
309 }
Florin Malita65fce9e2018-02-19 13:25:18 -0500310
311 const auto t = timer.msec() - fTimeBase;
312 fScene->animate(t);
313 fFocusController->tick(t);
Florin Malita76a076b2018-02-15 18:40:48 -0500314
315 return true;
316}
317
318bool SlideDir::onChar(SkUnichar c) {
Florin Malita65fce9e2018-02-19 13:25:18 -0500319 if (fFocusController->hasFocus()) {
320 if (c == kUnfocusKey) {
321 fFocusController->startUnfocus();
322 return true;
323 }
324 return fFocusController->onChar(c);
325 }
326
Florin Malita76a076b2018-02-15 18:40:48 -0500327 return false;
328}
329
Florin Malita65fce9e2018-02-19 13:25:18 -0500330bool SlideDir::onMouse(SkScalar x, SkScalar y, sk_app::Window::InputState state,
331 uint32_t modifiers) {
332 if (state == sk_app::Window::kMove_InputState || modifiers)
333 return false;
334
335 if (fFocusController->hasFocus()) {
336 return fFocusController->onMouse(x, y, state, modifiers);
337 }
338
339 const auto* cell = this->findCell(x, y);
340 if (!cell)
341 return false;
342
343 static constexpr SkScalar kClickMoveTolerance = 4;
344
345 switch (state) {
346 case sk_app::Window::kDown_InputState:
347 fTrackingCell = cell;
348 fTrackingPos = SkPoint::Make(x, y);
349 break;
350 case sk_app::Window::kUp_InputState:
351 if (cell == fTrackingCell &&
352 SkPoint::Distance(fTrackingPos, SkPoint::Make(x, y)) < kClickMoveTolerance) {
353
354 // Move the slide cell to front.
355 fRoot->removeChild(cell->fTransform);
356 fRoot->addChild(cell->fTransform);
357
358 fFocusController->startFocus(cell);
359 }
360 break;
361 default:
362 break;
363 }
364
Florin Malita76a076b2018-02-15 18:40:48 -0500365 return false;
366}
Florin Malita65fce9e2018-02-19 13:25:18 -0500367
368const SlideDir::Rec* SlideDir::findCell(float x, float y) const {
369 // TODO: use SG hit testing instead of layout info?
370 const auto size = this->getDimensions();
371 if (x < 0 || y < 0 || x >= size.width() || y >= size.height()) {
372 return nullptr;
373 }
374
375 const int col = static_cast<int>(x / fCellSize.width()),
376 row = static_cast<int>(y / fCellSize.height()),
377 idx = row * fColumns + col;
378
379 return idx <= fRecs.count() ? &fRecs[idx] : nullptr;
380}