f08d1d0ce1
Use std::swap instead. It does not appear that any external user specializes SkTSwap, but some may still use it. This removes all use in Skia so that SkTSwap can later be removed in a smaller CL. After that the <utility> include can be removed from SkTypes.h. Change-Id: If03d4ee07dbecda961aa9f0dc34d171ef5168753 Reviewed-on: https://skia-review.googlesource.com/135578 Reviewed-by: Hal Canary <halcanary@google.com> Reviewed-by: Mike Klein <mtklein@google.com> Commit-Queue: Ben Wagner <bungeman@google.com>
427 lines
12 KiB
C++
427 lines
12 KiB
C++
/*
|
|
* Copyright 2018 Google Inc.
|
|
*
|
|
* Use of this source code is governed by a BSD-style license that can be
|
|
* found in the LICENSE file.
|
|
*/
|
|
|
|
#include "SlideDir.h"
|
|
|
|
#ifdef SK_HAS_SKSG
|
|
|
|
#include "SkAnimTimer.h"
|
|
#include "SkCanvas.h"
|
|
#include "SkCubicMap.h"
|
|
#include "SkMakeUnique.h"
|
|
#include "SkSGColor.h"
|
|
#include "SkSGDraw.h"
|
|
#include "SkSGGroup.h"
|
|
#include "SkSGPlane.h"
|
|
#include "SkSGRect.h"
|
|
#include "SkSGRenderNode.h"
|
|
#include "SkSGScene.h"
|
|
#include "SkSGText.h"
|
|
#include "SkSGTransform.h"
|
|
#include "SkTypeface.h"
|
|
|
|
#include <cmath>
|
|
#include <utility>
|
|
|
|
namespace {
|
|
|
|
static constexpr float kAspectRatio = 1.5f;
|
|
static constexpr float kLabelSize = 12.0f;
|
|
static constexpr SkSize kPadding = { 12.0f , 24.0f };
|
|
|
|
static constexpr float kFocusDuration = 500;
|
|
static constexpr SkSize kFocusInset = { 100.0f, 100.0f };
|
|
static constexpr SkPoint kFocusCtrl0 = { 0.3f, 1.0f };
|
|
static constexpr SkPoint kFocusCtrl1 = { 0.0f, 1.0f };
|
|
static constexpr SkColor kFocusShade = 0xa0000000;
|
|
|
|
// TODO: better unfocus binding?
|
|
static constexpr SkUnichar kUnfocusKey = ' ';
|
|
|
|
class SlideAdapter final : public sksg::RenderNode {
|
|
public:
|
|
explicit SlideAdapter(sk_sp<Slide> slide)
|
|
: fSlide(std::move(slide)) {
|
|
SkASSERT(fSlide);
|
|
}
|
|
|
|
std::unique_ptr<sksg::Animator> makeForwardingAnimator() {
|
|
// Trivial sksg::Animator -> skottie::Animation tick adapter
|
|
class ForwardingAnimator final : public sksg::Animator {
|
|
public:
|
|
explicit ForwardingAnimator(sk_sp<SlideAdapter> adapter)
|
|
: fAdapter(std::move(adapter)) {}
|
|
|
|
protected:
|
|
void onTick(float t) override {
|
|
fAdapter->tick(SkScalarRoundToInt(t));
|
|
}
|
|
|
|
private:
|
|
sk_sp<SlideAdapter> fAdapter;
|
|
};
|
|
|
|
return skstd::make_unique<ForwardingAnimator>(sk_ref_sp(this));
|
|
}
|
|
|
|
protected:
|
|
SkRect onRevalidate(sksg::InvalidationController* ic, const SkMatrix& ctm) override {
|
|
const auto isize = fSlide->getDimensions();
|
|
return SkRect::MakeIWH(isize.width(), isize.height());
|
|
}
|
|
|
|
void onRender(SkCanvas* canvas) const override {
|
|
SkAutoCanvasRestore acr(canvas, true);
|
|
canvas->clipRect(SkRect::Make(fSlide->getDimensions()), true);
|
|
fSlide->draw(canvas);
|
|
}
|
|
|
|
private:
|
|
void tick(SkMSec t) {
|
|
fSlide->animate(SkAnimTimer(0, t * 1e6, SkAnimTimer::kRunning_State));
|
|
this->invalidate();
|
|
}
|
|
|
|
const sk_sp<Slide> fSlide;
|
|
|
|
using INHERITED = sksg::RenderNode;
|
|
};
|
|
|
|
SkMatrix SlideMatrix(const sk_sp<Slide>& slide, const SkRect& dst) {
|
|
const auto slideSize = slide->getDimensions();
|
|
return SkMatrix::MakeRectToRect(SkRect::MakeIWH(slideSize.width(), slideSize.height()),
|
|
dst,
|
|
SkMatrix::kCenter_ScaleToFit);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
struct SlideDir::Rec {
|
|
sk_sp<Slide> fSlide;
|
|
sk_sp<sksg::Transform> fTransform;
|
|
SkRect fRect;
|
|
};
|
|
|
|
class SlideDir::FocusController final : public sksg::Animator {
|
|
public:
|
|
FocusController(const SlideDir* dir, const SkRect& focusRect)
|
|
: fDir(dir)
|
|
, fRect(focusRect)
|
|
, fTarget(nullptr)
|
|
, fState(State::kIdle) {
|
|
fMap.setPts(kFocusCtrl1, kFocusCtrl0);
|
|
|
|
fShadePaint = sksg::Color::Make(kFocusShade);
|
|
fShade = sksg::Draw::Make(sksg::Plane::Make(), fShadePaint);
|
|
}
|
|
|
|
bool hasFocus() const { return fState == State::kFocused; }
|
|
|
|
void startFocus(const Rec* target) {
|
|
if (fState != State::kIdle)
|
|
return;
|
|
|
|
fTarget = target;
|
|
|
|
// Move the shade & slide to front.
|
|
fDir->fRoot->removeChild(fTarget->fTransform);
|
|
fDir->fRoot->addChild(fShade);
|
|
fDir->fRoot->addChild(fTarget->fTransform);
|
|
|
|
fM0 = SlideMatrix(fTarget->fSlide, fTarget->fRect);
|
|
fM1 = SlideMatrix(fTarget->fSlide, fRect);
|
|
|
|
fOpacity0 = 0;
|
|
fOpacity1 = 1;
|
|
|
|
fTimeBase = 0;
|
|
fState = State::kFocusing;
|
|
|
|
// Push initial state to the scene graph.
|
|
this->onTick(fTimeBase);
|
|
}
|
|
|
|
void startUnfocus() {
|
|
SkASSERT(fTarget);
|
|
|
|
using std::swap;
|
|
swap(fM0, fM1);
|
|
swap(fOpacity0, fOpacity1);
|
|
|
|
fTimeBase = 0;
|
|
fState = State::kUnfocusing;
|
|
}
|
|
|
|
bool onMouse(SkScalar x, SkScalar y, sk_app::Window::InputState state, uint32_t modifiers) {
|
|
SkASSERT(fTarget);
|
|
|
|
if (!fRect.contains(x, y)) {
|
|
this->startUnfocus();
|
|
return true;
|
|
}
|
|
|
|
// Map coords to slide space.
|
|
const auto xform = SkMatrix::MakeRectToRect(fRect,
|
|
SkRect::MakeSize(fDir->fWinSize),
|
|
SkMatrix::kCenter_ScaleToFit);
|
|
const auto pt = xform.mapXY(x, y);
|
|
|
|
return fTarget->fSlide->onMouse(pt.x(), pt.y(), state, modifiers);
|
|
}
|
|
|
|
bool onChar(SkUnichar c) {
|
|
SkASSERT(fTarget);
|
|
|
|
return fTarget->fSlide->onChar(c);
|
|
}
|
|
|
|
protected:
|
|
void onTick(float t) {
|
|
if (!this->isAnimating())
|
|
return;
|
|
|
|
if (!fTimeBase) {
|
|
fTimeBase = t;
|
|
}
|
|
|
|
const auto rel_t = (t - fTimeBase) / kFocusDuration,
|
|
map_t = SkTPin(fMap.computeYFromX(rel_t), 0.0f, 1.0f);
|
|
|
|
SkMatrix m;
|
|
for (int i = 0; i < 9; ++i) {
|
|
m[i] = fM0[i] + map_t * (fM1[i] - fM0[i]);
|
|
}
|
|
|
|
SkASSERT(fTarget);
|
|
fTarget->fTransform->getMatrix()->setMatrix(m);
|
|
|
|
const auto shadeOpacity = fOpacity0 + map_t * (fOpacity1 - fOpacity0);
|
|
fShadePaint->setOpacity(shadeOpacity);
|
|
|
|
if (rel_t < 1)
|
|
return;
|
|
|
|
switch (fState) {
|
|
case State::kFocusing:
|
|
fState = State::kFocused;
|
|
break;
|
|
case State::kUnfocusing:
|
|
fState = State::kIdle;
|
|
fDir->fRoot->removeChild(fShade);
|
|
break;
|
|
|
|
case State::kIdle:
|
|
case State::kFocused:
|
|
SkASSERT(false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
private:
|
|
enum class State {
|
|
kIdle,
|
|
kFocusing,
|
|
kUnfocusing,
|
|
kFocused,
|
|
};
|
|
|
|
bool isAnimating() const { return fState == State::kFocusing || fState == State::kUnfocusing; }
|
|
|
|
const SlideDir* fDir;
|
|
const SkRect fRect;
|
|
const Rec* fTarget;
|
|
|
|
SkCubicMap fMap;
|
|
sk_sp<sksg::RenderNode> fShade;
|
|
sk_sp<sksg::PaintNode> fShadePaint;
|
|
|
|
SkMatrix fM0 = SkMatrix::I(),
|
|
fM1 = SkMatrix::I();
|
|
float fOpacity0 = 0,
|
|
fOpacity1 = 1,
|
|
fTimeBase = 0;
|
|
State fState = State::kIdle;
|
|
|
|
using INHERITED = sksg::Animator;
|
|
};
|
|
|
|
SlideDir::SlideDir(const SkString& name, SkTArray<sk_sp<Slide>, true>&& slides, int columns)
|
|
: fSlides(std::move(slides))
|
|
, fColumns(columns) {
|
|
fName = name;
|
|
}
|
|
|
|
static sk_sp<sksg::RenderNode> MakeLabel(const SkString& txt,
|
|
const SkPoint& pos,
|
|
const SkMatrix& dstXform) {
|
|
const auto size = kLabelSize / std::sqrt(dstXform.getScaleX() * dstXform.getScaleY());
|
|
auto text = sksg::Text::Make(nullptr, txt);
|
|
text->setFlags(SkPaint::kAntiAlias_Flag);
|
|
text->setSize(size);
|
|
text->setAlign(SkPaint::kCenter_Align);
|
|
text->setPosition(pos + SkPoint::Make(0, size));
|
|
|
|
return sksg::Draw::Make(std::move(text), sksg::Color::Make(SK_ColorBLACK));
|
|
}
|
|
|
|
void SlideDir::load(SkScalar winWidth, SkScalar winHeight) {
|
|
// Build a global scene using transformed animation fragments:
|
|
//
|
|
// [Group(root)]
|
|
// [Transform]
|
|
// [Group]
|
|
// [AnimationWrapper]
|
|
// [Draw]
|
|
// [Text]
|
|
// [Color]
|
|
// [Transform]
|
|
// [Group]
|
|
// [AnimationWrapper]
|
|
// [Draw]
|
|
// [Text]
|
|
// [Color]
|
|
// ...
|
|
//
|
|
|
|
fWinSize = SkSize::Make(winWidth, winHeight);
|
|
const auto cellWidth = winWidth / fColumns;
|
|
fCellSize = SkSize::Make(cellWidth, cellWidth / kAspectRatio);
|
|
|
|
sksg::AnimatorList sceneAnimators;
|
|
fRoot = sksg::Group::Make();
|
|
|
|
for (int i = 0; i < fSlides.count(); ++i) {
|
|
const auto& slide = fSlides[i];
|
|
slide->load(winWidth, winHeight);
|
|
|
|
const auto slideSize = slide->getDimensions();
|
|
const auto cell = SkRect::MakeXYWH(fCellSize.width() * (i % fColumns),
|
|
fCellSize.height() * (i / fColumns),
|
|
fCellSize.width(),
|
|
fCellSize.height()),
|
|
slideRect = cell.makeInset(kPadding.width(), kPadding.height());
|
|
|
|
auto slideMatrix = SlideMatrix(slide, slideRect);
|
|
auto adapter = sk_make_sp<SlideAdapter>(slide);
|
|
auto slideGrp = sksg::Group::Make();
|
|
slideGrp->addChild(sksg::Draw::Make(sksg::Rect::Make(SkRect::MakeIWH(slideSize.width(),
|
|
slideSize.height())),
|
|
sksg::Color::Make(0xfff0f0f0)));
|
|
slideGrp->addChild(adapter);
|
|
slideGrp->addChild(MakeLabel(slide->getName(),
|
|
SkPoint::Make(slideSize.width() / 2, slideSize.height()),
|
|
slideMatrix));
|
|
auto slideTransform = sksg::Transform::Make(std::move(slideGrp), slideMatrix);
|
|
|
|
sceneAnimators.push_back(adapter->makeForwardingAnimator());
|
|
|
|
fRoot->addChild(slideTransform);
|
|
fRecs.push_back({ slide, slideTransform, slideRect });
|
|
}
|
|
|
|
fScene = sksg::Scene::Make(fRoot, std::move(sceneAnimators));
|
|
|
|
const auto focusRect = SkRect::MakeSize(fWinSize).makeInset(kFocusInset.width(),
|
|
kFocusInset.height());
|
|
fFocusController = skstd::make_unique<FocusController>(this, focusRect);
|
|
}
|
|
|
|
void SlideDir::unload() {
|
|
for (const auto& slide : fSlides) {
|
|
slide->unload();
|
|
}
|
|
|
|
fRecs.reset();
|
|
fScene.reset();
|
|
fFocusController.reset();
|
|
fRoot.reset();
|
|
fTimeBase = 0;
|
|
}
|
|
|
|
SkISize SlideDir::getDimensions() const {
|
|
return SkSize::Make(fWinSize.width(),
|
|
fCellSize.height() * (1 + (fSlides.count() - 1) / fColumns)).toCeil();
|
|
}
|
|
|
|
void SlideDir::draw(SkCanvas* canvas) {
|
|
fScene->render(canvas);
|
|
}
|
|
|
|
bool SlideDir::animate(const SkAnimTimer& timer) {
|
|
if (fTimeBase == 0) {
|
|
// Reset the animation time.
|
|
fTimeBase = timer.msec();
|
|
}
|
|
|
|
const auto t = timer.msec() - fTimeBase;
|
|
fScene->animate(t);
|
|
fFocusController->tick(t);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool SlideDir::onChar(SkUnichar c) {
|
|
if (fFocusController->hasFocus()) {
|
|
if (c == kUnfocusKey) {
|
|
fFocusController->startUnfocus();
|
|
return true;
|
|
}
|
|
return fFocusController->onChar(c);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool SlideDir::onMouse(SkScalar x, SkScalar y, sk_app::Window::InputState state,
|
|
uint32_t modifiers) {
|
|
if (state == sk_app::Window::kMove_InputState || modifiers)
|
|
return false;
|
|
|
|
if (fFocusController->hasFocus()) {
|
|
return fFocusController->onMouse(x, y, state, modifiers);
|
|
}
|
|
|
|
const auto* cell = this->findCell(x, y);
|
|
if (!cell)
|
|
return false;
|
|
|
|
static constexpr SkScalar kClickMoveTolerance = 4;
|
|
|
|
switch (state) {
|
|
case sk_app::Window::kDown_InputState:
|
|
fTrackingCell = cell;
|
|
fTrackingPos = SkPoint::Make(x, y);
|
|
break;
|
|
case sk_app::Window::kUp_InputState:
|
|
if (cell == fTrackingCell &&
|
|
SkPoint::Distance(fTrackingPos, SkPoint::Make(x, y)) < kClickMoveTolerance) {
|
|
fFocusController->startFocus(cell);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
const SlideDir::Rec* SlideDir::findCell(float x, float y) const {
|
|
// TODO: use SG hit testing instead of layout info?
|
|
const auto size = this->getDimensions();
|
|
if (x < 0 || y < 0 || x >= size.width() || y >= size.height()) {
|
|
return nullptr;
|
|
}
|
|
|
|
const int col = static_cast<int>(x / fCellSize.width()),
|
|
row = static_cast<int>(y / fCellSize.height()),
|
|
idx = row * fColumns + col;
|
|
|
|
return idx < fRecs.count() ? &fRecs[idx] : nullptr;
|
|
}
|
|
|
|
#endif // SK_HAS_SKSG
|