[sksg] Hit-testing API

Introduce RenderNode::nodeAt(const SkPoint&) as the entry point for the hit-testing API.

This is backed by a onNodeAt() virtual, which gets dispatched throughout the render DAG,
and normally stops at the first leaf Draw node in encounters.

To support the implementation, introduce a GeometryNode::contains(const SkPoint&) API.

This is backed by a onContains() virtual, overridden in each concrete geometry class.

Expose nodeAt() on sksg::Scene, and add some basic unit tests.

Change-Id: I0c8abd9d1e51ecf2d8b4dd699f325cd636e21084
Reviewed-on: https://skia-review.googlesource.com/c/191296
Commit-Queue: Florin Malita <fmalita@chromium.org>
Reviewed-by: Mike Reed <reed@google.com>
This commit is contained in:
Florin Malita 2019-02-12 09:33:21 -05:00 committed by Skia Commit-Bot
parent 01d5ec8b9f
commit eb46bd892d
45 changed files with 266 additions and 3 deletions

View File

@ -187,6 +187,8 @@ sk_sp<sksg::RenderNode> AnimationBuilder::attachNestedAnimation(const char* name
return SkRect::MakeSize(fAnimation->size());
}
const RenderNode* onNodeAt(const SkPoint&) const override { return nullptr; }
void onRender(SkCanvas* canvas, const RenderContext* ctx) const override {
const auto local_scope =
ScopedRenderContext(canvas, ctx).setIsolation(this->bounds(), true);

View File

@ -33,6 +33,7 @@ protected:
ClipEffect(sk_sp<RenderNode>, sk_sp<GeometryNode>, bool aa);
void onRender(SkCanvas*, const RenderContext*) const override;
const RenderNode* onNodeAt(const SkPoint&) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;

View File

@ -27,6 +27,7 @@ protected:
explicit ColorFilter(sk_sp<RenderNode>);
void onRender(SkCanvas*, const RenderContext*) const final;
const RenderNode* onNodeAt(const SkPoint&) const final;
sk_sp<SkColorFilter> fColorFilter;

View File

@ -33,6 +33,7 @@ protected:
~Draw() override;
void onRender(SkCanvas*, const RenderContext*) const override;
const RenderNode* onNodeAt(const SkPoint&) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;

View File

@ -24,6 +24,7 @@ protected:
~EffectNode() override;
void onRender(SkCanvas*, const RenderContext*) const override;
const RenderNode* onNodeAt(const SkPoint&) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;

View File

@ -27,6 +27,8 @@ public:
void clip(SkCanvas*, bool antiAlias) const;
void draw(SkCanvas*, const SkPaint&) const;
bool contains(const SkPoint&) const;
SkPath asPath() const;
protected:
@ -36,6 +38,8 @@ protected:
virtual void onDraw(SkCanvas*, const SkPaint&) const = 0;
virtual bool onContains(const SkPoint&) const = 0;
virtual SkPath onAsPath() const = 0;
private:

View File

@ -37,6 +37,7 @@ public:
protected:
void onClip(SkCanvas*, bool antiAlias) const override;
void onDraw(SkCanvas*, const SkPaint&) const override;
bool onContains(const SkPoint&) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
SkPath onAsPath() const override;

View File

@ -39,6 +39,8 @@ protected:
~Group() override;
void onRender(SkCanvas*, const RenderContext*) const override;
const RenderNode* onNodeAt(const SkPoint&) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
private:

View File

@ -34,6 +34,7 @@ protected:
explicit Image(sk_sp<SkImage>);
void onRender(SkCanvas*, const RenderContext*) const override;
const RenderNode* onNodeAt(const SkPoint&) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;

View File

@ -36,6 +36,7 @@ protected:
MaskEffect(sk_sp<RenderNode>, sk_sp<RenderNode> mask, Mode);
void onRender(SkCanvas*, const RenderContext*) const override;
const RenderNode* onNodeAt(const SkPoint&) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;

View File

@ -50,6 +50,7 @@ public:
protected:
void onClip(SkCanvas*, bool antiAlias) const override;
void onDraw(SkCanvas*, const SkPaint&) const override;
bool onContains(const SkPoint&) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
SkPath onAsPath() const override;

View File

@ -28,6 +28,7 @@ protected:
OpacityEffect(sk_sp<RenderNode>, float);
void onRender(SkCanvas*, const RenderContext*) const override;
const RenderNode* onNodeAt(const SkPoint&) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;

View File

@ -31,6 +31,7 @@ public:
protected:
void onClip(SkCanvas*, bool antiAlias) const override;
void onDraw(SkCanvas*, const SkPaint&) const override;
bool onContains(const SkPoint&) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
SkPath onAsPath() const override;

View File

@ -25,6 +25,7 @@ public:
protected:
void onClip(SkCanvas*, bool antiAlias) const override;
void onDraw(SkCanvas*, const SkPaint&) const override;
bool onContains(const SkPoint&) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
SkPath onAsPath() const override;

View File

@ -38,6 +38,7 @@ public:
protected:
void onClip(SkCanvas*, bool antiAlias) const override;
void onDraw(SkCanvas*, const SkPaint&) const override;
bool onContains(const SkPoint&) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
SkPath onAsPath() const override;
@ -80,6 +81,7 @@ public:
protected:
void onClip(SkCanvas*, bool antiAlias) const override;
void onDraw(SkCanvas*, const SkPaint&) const override;
bool onContains(const SkPoint&) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
SkPath onAsPath() const override;

View File

@ -64,6 +64,7 @@ public:
protected:
void onRender(SkCanvas*, const RenderContext*) const override;
const RenderNode* onNodeAt(const SkPoint&) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;

View File

@ -29,10 +29,15 @@ public:
// Render the node and its descendants to the canvas.
void render(SkCanvas*, const RenderContext* = nullptr) const;
// Perform a front-to-back hit-test, and return the RenderNode located at |point|.
// Normally, hit-testing stops at leaf Draw nodes.
const RenderNode* nodeAt(const SkPoint& point) const;
protected:
explicit RenderNode(uint32_t inval_traits = 0);
virtual void onRender(SkCanvas*, const RenderContext*) const = 0;
virtual const RenderNode* onNodeAt(const SkPoint& p) const = 0;
// Paint property overrides.
// These are deferred until we can determine whether they can be applied to the individual

View File

@ -30,6 +30,7 @@ public:
protected:
void onClip(SkCanvas*, bool antiAlias) const override;
void onDraw(SkCanvas*, const SkPaint&) const override;
bool onContains(const SkPoint&) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
SkPath onAsPath() const override;

View File

@ -15,6 +15,7 @@
#include <vector>
class SkCanvas;
struct SkPoint;
namespace sksg {
@ -67,6 +68,7 @@ public:
void render(SkCanvas*) const;
void animate(float t);
const RenderNode* nodeAt(const SkPoint&) const;
void setShowInval(bool show) { fShowInval = show; }

View File

@ -45,6 +45,7 @@ public:
protected:
void onClip(SkCanvas*, bool antiAlias) const override;
void onDraw(SkCanvas*, const SkPaint&) const override;
bool onContains(const SkPoint&) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
SkPath onAsPath() const override;
@ -83,6 +84,7 @@ public:
protected:
void onClip(SkCanvas*, bool antiAlias) const override;
void onDraw(SkCanvas*, const SkPaint&) const override;
bool onContains(const SkPoint&) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
SkPath onAsPath() const override;

View File

@ -97,6 +97,7 @@ public:
protected:
void onRender(SkCanvas*, const RenderContext*) const override;
const RenderNode* onNodeAt(const SkPoint&) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;

View File

@ -36,6 +36,7 @@ public:
protected:
void onClip(SkCanvas*, bool antiAlias) const override;
void onDraw(SkCanvas*, const SkPaint&) const override;
bool onContains(const SkPoint&) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
SkPath onAsPath() const override;

View File

@ -36,6 +36,10 @@ void ClipEffect::onRender(SkCanvas* canvas, const RenderContext* ctx) const {
this->INHERITED::onRender(canvas, ctx);
}
const RenderNode* ClipEffect::onNodeAt(const SkPoint& p) const {
return fClipNode->contains(p) ? this->INHERITED::onNodeAt(p) : nullptr;
}
SkRect ClipEffect::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
SkASSERT(this->hasInval());

View File

@ -24,6 +24,11 @@ void ColorFilter::onRender(SkCanvas* canvas, const RenderContext* ctx) const {
this->INHERITED::onRender(canvas, local_ctx);
}
const RenderNode* ColorFilter::onNodeAt(const SkPoint& p) const {
// TODO: we likely need to do something more sophisticated than delegate to descendants here.
return this->INHERITED::onNodeAt(p);
}
ColorModeFilter::ColorModeFilter(sk_sp<RenderNode> child, sk_sp<Color> color, SkBlendMode mode)
: INHERITED(std::move(child))
, fColor(std::move(color))

View File

@ -7,6 +7,7 @@
#include "SkSGDraw.h"
#include "SkPath.h"
#include "SkSGGeometryNode.h"
#include "SkSGInvalidationController.h"
#include "SkSGPaintNode.h"
@ -40,6 +41,25 @@ void Draw::onRender(SkCanvas* canvas, const RenderContext* ctx) const {
}
}
const RenderNode* Draw::onNodeAt(const SkPoint& p) const {
const auto paint = fPaint->makePaint();
if (!paint.getAlpha()) {
return nullptr;
}
if (paint.getStyle() == SkPaint::Style::kFill_Style && fGeometry->contains(p)) {
return this;
}
SkPath stroke_path;
if (!paint.getFillPath(fGeometry->asPath(), &stroke_path)) {
return nullptr;
}
return stroke_path.contains(p.x(), p.y()) ? this : nullptr;
}
SkRect Draw::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
SkASSERT(this->hasInval());

View File

@ -23,6 +23,10 @@ void EffectNode::onRender(SkCanvas* canvas, const RenderContext* ctx) const {
fChild->render(canvas, ctx);
}
const RenderNode* EffectNode::onNodeAt(const SkPoint& p) const {
return fChild->nodeAt(p);
}
SkRect EffectNode::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
SkASSERT(this->hasInval());

View File

@ -24,6 +24,11 @@ void GeometryNode::draw(SkCanvas* canvas, const SkPaint& paint) const {
this->onDraw(canvas, paint);
}
bool GeometryNode::contains(const SkPoint& p) const {
SkASSERT(!this->hasInval());
return this->bounds().contains(p.x(), p.y()) ? this->onContains(p) : false;
}
SkPath GeometryNode::asPath() const {
SkASSERT(!this->hasInval());
return this->onAsPath();

View File

@ -33,6 +33,10 @@ void GeometryTransform::onDraw(SkCanvas* canvas, const SkPaint& paint) const {
canvas->drawPath(fTransformedPath, paint);
}
bool GeometryTransform::onContains(const SkPoint& p) const {
return fTransformedPath.contains(p.x(), p.y());
}
SkRect GeometryTransform::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
SkASSERT(this->hasInval());

View File

@ -66,6 +66,16 @@ void Group::onRender(SkCanvas* canvas, const RenderContext* ctx) const {
}
}
const RenderNode* Group::onNodeAt(const SkPoint& p) const {
for (const auto& child : fChildren) {
if (const auto* node = child->nodeAt(p)) {
return node;
}
}
return nullptr;
}
SkRect Group::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
SkASSERT(this->hasInval());

View File

@ -30,6 +30,11 @@ void Image::onRender(SkCanvas* canvas, const RenderContext* ctx) const {
canvas->drawImage(fImage, 0, 0, &paint);
}
const RenderNode* Image::onNodeAt(const SkPoint& p) const {
SkASSERT(this->bounds().contains(p.x(), p.y()));
return this;
}
SkRect Image::onRevalidate(InvalidationController*, const SkMatrix& ctm) {
return fImage ? SkRect::Make(fImage->bounds()) : SkRect::MakeEmpty();
}

View File

@ -32,7 +32,6 @@ void MaskEffect::onRender(SkCanvas* canvas, const RenderContext* ctx) const {
// Note: the paint overrides in ctx don't apply to the mask.
fMaskNode->render(canvas);
SkPaint p;
p.setBlendMode(fMaskMode == Mode::kNormal ? SkBlendMode::kSrcIn : SkBlendMode::kSrcOut);
canvas->saveLayer(this->bounds(), &p);
@ -40,6 +39,11 @@ void MaskEffect::onRender(SkCanvas* canvas, const RenderContext* ctx) const {
this->INHERITED::onRender(canvas, ctx);
}
const RenderNode* MaskEffect::onNodeAt(const SkPoint& p) const {
const auto mask_hit = (!!fMaskNode->nodeAt(p) == (fMaskMode == Mode::kNormal));
return mask_hit ? this->INHERITED::onNodeAt(p) : nullptr;
}
SkRect MaskEffect::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
SkASSERT(this->hasInval());

View File

@ -33,6 +33,10 @@ void Merge::onDraw(SkCanvas* canvas, const SkPaint& paint) const {
canvas->drawPath(fMerged, paint);
}
bool Merge::onContains(const SkPoint& p) const {
return fMerged.contains(p.x(), p.y());
}
SkPath Merge::onAsPath() const {
return fMerged;
}

View File

@ -23,6 +23,10 @@ void OpacityEffect::onRender(SkCanvas* canvas, const RenderContext* ctx) const {
this->INHERITED::onRender(canvas, local_context);
}
const RenderNode* OpacityEffect::onNodeAt(const SkPoint& p) const {
return (fOpacity > 0) ? this->INHERITED::onNodeAt(p) : nullptr;
}
SkRect OpacityEffect::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
SkASSERT(this->hasInval());

View File

@ -23,6 +23,10 @@ void Path::onDraw(SkCanvas* canvas, const SkPaint& paint) const {
canvas->drawPath(fPath, paint);
}
bool Path::onContains(const SkPoint& p) const {
return fPath.contains(p.x(), p.y());
}
SkRect Path::onRevalidate(InvalidationController*, const SkMatrix&) {
SkASSERT(this->hasInval());

View File

@ -20,6 +20,8 @@ void Plane::onDraw(SkCanvas* canvas, const SkPaint& paint) const {
canvas->drawPaint(paint);
}
bool Plane::onContains(const SkPoint&) const { return true; }
SkRect Plane::onRevalidate(InvalidationController*, const SkMatrix&) {
SkASSERT(this->hasInval());

View File

@ -23,6 +23,10 @@ void Rect::onDraw(SkCanvas* canvas, const SkPaint& paint) const {
canvas->drawRect(fRect, paint);
}
bool Rect::onContains(const SkPoint& p) const {
return fRect.contains(p.x(), p.y());
}
SkRect Rect::onRevalidate(InvalidationController*, const SkMatrix&) {
SkASSERT(this->hasInval());
@ -45,6 +49,22 @@ void RRect::onDraw(SkCanvas* canvas, const SkPaint& paint) const {
canvas->drawRRect(fRRect, paint);
}
bool RRect::onContains(const SkPoint& p) const {
if (!fRRect.rect().contains(p.x(), p.y())) {
return false;
}
if (fRRect.isRect()) {
return true;
}
// TODO: no SkRRect::contains(x, y)
return fRRect.contains(SkRect::MakeLTRB(p.x() - SK_ScalarNearlyZero,
p.y() - SK_ScalarNearlyZero,
p.x() + SK_ScalarNearlyZero,
p.y() + SK_ScalarNearlyZero));
}
SkRect RRect::onRevalidate(InvalidationController*, const SkMatrix&) {
SkASSERT(this->hasInval());

View File

@ -39,6 +39,13 @@ SkRect ImageFilterEffect::onRevalidate(InvalidationController* ic, const SkMatri
return filter->computeFastBounds(this->INHERITED::onRevalidate(ic, ctm));
}
const RenderNode* ImageFilterEffect::onNodeAt(const SkPoint& p) const {
// TODO: map p through the filter DAG and dispatch to descendants?
// For now, image filters occlude hit-testing.
SkASSERT(this->bounds().contains(p.x(), p.y()));
return this;
}
void ImageFilterEffect::onRender(SkCanvas* canvas, const RenderContext* ctx) const {
// TODO: hoist these checks to RenderNode?
if (this->bounds().isEmpty())

View File

@ -20,6 +20,10 @@ void RenderNode::render(SkCanvas* canvas, const RenderContext* ctx) const {
this->onRender(canvas, ctx);
}
const RenderNode* RenderNode::nodeAt(const SkPoint& p) const {
return this->bounds().contains(p.x(), p.y()) ? this->onNodeAt(p) : nullptr;
}
bool RenderNode::RenderContext::modulatePaint(SkPaint* paint) const {
const auto initial_alpha = paint->getAlpha(),
alpha = SkToU8(sk_float_round2int(initial_alpha * fOpacity));

View File

@ -32,6 +32,10 @@ void RoundEffect::onDraw(SkCanvas* canvas, const SkPaint& paint) const {
canvas->drawPath(fRoundedPath, paint);
}
bool RoundEffect::onContains(const SkPoint& p) const {
return fRoundedPath.contains(p.x(), p.y());
}
SkPath RoundEffect::onAsPath() const {
return fRoundedPath;
}

View File

@ -69,4 +69,8 @@ void Scene::animate(float t) {
}
}
const RenderNode* Scene::nodeAt(const SkPoint& p) const {
return fRoot->nodeAt(p);
}
} // namespace sksg

View File

@ -73,6 +73,10 @@ void Text::onDraw(SkCanvas* canvas, const SkPaint& paint) const {
canvas->drawTextBlob(fBlob, aligned_pos.x(), aligned_pos.y(), paint);
}
bool Text::onContains(const SkPoint& p) const {
return this->asPath().contains(p.x(), p.y());
}
SkPath Text::onAsPath() const {
// TODO
return SkPath();
@ -99,6 +103,10 @@ void TextBlob::onDraw(SkCanvas* canvas, const SkPaint& paint) const {
canvas->drawTextBlob(fBlob, fPosition.x(), fPosition.y(), paint);
}
bool TextBlob::onContains(const SkPoint& p) const {
return this->asPath().contains(p.x(), p.y());
}
SkPath TextBlob::onAsPath() const {
// TODO
return SkPath();

View File

@ -96,6 +96,15 @@ void TransformEffect::onRender(SkCanvas* canvas, const RenderContext* ctx) const
this->INHERITED::onRender(canvas, ctx);
}
const RenderNode* TransformEffect::onNodeAt(const SkPoint& p) const {
const auto m = TransformPriv::As<SkMatrix>(fTransform);
SkPoint mapped_p;
m.mapPoints(&mapped_p, &p, 1);
return this->INHERITED::onNodeAt(mapped_p);
}
SkRect TransformEffect::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
SkASSERT(this->hasInval());

View File

@ -32,6 +32,10 @@ void TrimEffect::onDraw(SkCanvas* canvas, const SkPaint& paint) const {
canvas->drawPath(fTrimmedPath, paint);
}
bool TrimEffect::onContains(const SkPoint& p) const {
return fTrimmedPath.contains(p.x(), p.y());
}
SkPath TrimEffect::onAsPath() const {
return fTrimmedPath;
}

View File

@ -56,6 +56,23 @@ static void check_inval(skiatest::Reporter* reporter, const sk_sp<sksg::Node>& r
}
}
struct HitTest {
const SkPoint pt;
sk_sp<sksg::RenderNode> node;
};
static void check_hittest(skiatest::Reporter* reporter, const sk_sp<sksg::RenderNode>& root,
const std::vector<HitTest>& tests) {
for (const auto& tst : tests) {
const auto* node = root->nodeAt(tst.pt);
if (node != tst.node.get()) {
SkDebugf("*** nodeAt(%f, %f) - expected %p, got %p\n",
tst.pt.x(), tst.pt.y(), tst.node.get(), node);
}
REPORTER_ASSERT(reporter, tst.node.get() == node);
}
}
static void inval_test1(skiatest::Reporter* reporter) {
auto color = sksg::Color::Make(0xff000000);
auto r1 = sksg::Rect::Make(SkRect::MakeWH(100, 100)),
@ -63,9 +80,11 @@ static void inval_test1(skiatest::Reporter* reporter) {
auto grp = sksg::Group::Make();
auto matrix = sksg::Matrix<SkMatrix>::Make(SkMatrix::I());
auto root = sksg::TransformEffect::Make(grp, matrix);
auto d1 = sksg::Draw::Make(r1, color),
d2 = sksg::Draw::Make(r2, color);
grp->addChild(sksg::Draw::Make(r1, color));
grp->addChild(sksg::Draw::Make(r2, color));
grp->addChild(d1);
grp->addChild(d2);
{
// Initial revalidation.
@ -73,6 +92,15 @@ static void inval_test1(skiatest::Reporter* reporter) {
SkRect::MakeWH(100, 100),
SkRectPriv::MakeLargeS32(),
nullptr);
check_hittest(reporter, root, {
{{ -1, 0 }, nullptr },
{{ 0, -1 }, nullptr },
{{ 100, 0 }, nullptr },
{{ 0, 100 }, nullptr },
{{ 0, 0 }, d1 },
{{ 99, 99 }, d1 },
});
}
{
@ -83,6 +111,22 @@ static void inval_test1(skiatest::Reporter* reporter) {
SkRect::MakeWH(300, 200),
SkRect::MakeWH(300, 200),
&damage);
check_hittest(reporter, root, {
{{ -1, 0 }, nullptr },
{{ 0, -1 }, nullptr },
{{ 100, 0 }, nullptr },
{{ 0, 100 }, nullptr },
{{ 0, 0 }, d1 },
{{ 99, 99 }, d1 },
{{ 199, 100 }, nullptr },
{{ 200, 99 }, nullptr },
{{ 300, 100 }, nullptr },
{{ 200, 200 }, nullptr },
{{ 200, 100 }, d2 },
{{ 299, 199 }, d2 },
});
}
{
@ -103,6 +147,22 @@ static void inval_test1(skiatest::Reporter* reporter) {
SkRect::MakeWH(300, 200),
SkRect::MakeWH(100, 100),
&damage);
check_hittest(reporter, root, {
{{ -1, 0 }, nullptr },
{{ 0, -1 }, nullptr },
{{ 50, 0 }, nullptr },
{{ 0, 100 }, nullptr },
{{ 0, 0 }, d1 },
{{ 49, 99 }, d1 },
{{ 199, 100 }, nullptr },
{{ 200, 99 }, nullptr },
{{ 300, 100 }, nullptr },
{{ 200, 200 }, nullptr },
{{ 200, 100 }, d2 },
{{ 299, 199 }, d2 },
});
}
{
@ -113,6 +173,22 @@ static void inval_test1(skiatest::Reporter* reporter) {
SkRect::MakeWH(600, 400),
SkRect::MakeWH(600, 400),
&damage);
check_hittest(reporter, root, {
{{ -1, 0 }, nullptr },
{{ 0, -1 }, nullptr },
{{ 25, 0 }, nullptr },
{{ 0, 50 }, nullptr },
{{ 0, 0 }, d1 },
{{ 24, 49 }, d1 },
{{ 99, 50 }, nullptr },
{{ 100, 49 }, nullptr },
{{ 150, 50 }, nullptr },
{{ 100, 100 }, nullptr },
{{ 100, 50 }, d2 },
{{ 149, 99 }, d2 },
});
}
{
@ -123,6 +199,22 @@ static void inval_test1(skiatest::Reporter* reporter) {
SkRect::MakeWH(500, 400),
SkRect::MakeLTRB(400, 200, 600, 400),
&damage);
check_hittest(reporter, root, {
{{ -1, 0 }, nullptr },
{{ 0, -1 }, nullptr },
{{ 25, 0 }, nullptr },
{{ 0, 50 }, nullptr },
{{ 0, 0 }, d1 },
{{ 24, 49 }, d1 },
{{ 99, 50 }, nullptr },
{{ 100, 49 }, nullptr },
{{ 125, 50 }, nullptr },
{{ 100, 100 }, nullptr },
{{ 100, 50 }, d2 },
{{ 124, 99 }, d2 },
});
}
}

View File

@ -80,6 +80,8 @@ protected:
fSlide->draw(canvas);
}
const RenderNode* onNodeAt(const SkPoint&) const override { return nullptr; }
private:
void tick(SkMSec t) {
fSlide->animate(SkAnimTimer(t * 1e6));