Merge changes I4f72448f,I74b7233c into nyc-dev
am: b67985f337
* commit 'b67985f337e1592f621e1b85f525098887804ccd':
Fix ripple positioning within scrolled node
Clip projected ripples to outlines
This commit is contained in:
@@ -30,6 +30,7 @@
|
||||
#include <algorithm>
|
||||
#include <math.h>
|
||||
#include <SkPaintDefaults.h>
|
||||
#include <SkPathOps.h>
|
||||
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
@@ -527,6 +528,12 @@ void BakedOpDispatcher::onOvalOp(BakedOpRenderer& renderer, const OvalOp& op, co
|
||||
SkPath path;
|
||||
SkRect rect = getBoundsOfFill(op);
|
||||
path.addOval(rect);
|
||||
|
||||
if (state.computedState.localProjectionPathMask != nullptr) {
|
||||
// Mask the ripple path by the local space projection mask in local space.
|
||||
// Note that this can create CCW paths.
|
||||
Op(path, *state.computedState.localProjectionPathMask, kIntersect_SkPathOp, &path);
|
||||
}
|
||||
renderConvexPath(renderer, state, path, *(op.paint));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,9 +63,22 @@ ResolvedRenderState::ResolvedRenderState(LinearAllocator& allocator, Snapshot& s
|
||||
clipState = nullptr;
|
||||
clippedBounds.setEmpty();
|
||||
} else {
|
||||
// Not rejected! compute true clippedBounds and clipSideFlags
|
||||
// Not rejected! compute true clippedBounds, clipSideFlags, and path mask
|
||||
clipSideFlags = computeClipSideFlags(clipRect, clippedBounds);
|
||||
clippedBounds.doIntersect(clipRect);
|
||||
|
||||
if (CC_UNLIKELY(snapshot.projectionPathMask)) {
|
||||
// map projection path mask from render target space into op space,
|
||||
// so intersection with op geometry is possible
|
||||
Matrix4 inverseTransform;
|
||||
inverseTransform.loadInverse(transform);
|
||||
SkMatrix skInverseTransform;
|
||||
inverseTransform.copyTo(skInverseTransform);
|
||||
|
||||
auto localMask = allocator.create<SkPath>();
|
||||
snapshot.projectionPathMask->transform(skInverseTransform, localMask);
|
||||
localProjectionPathMask = localMask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,13 +86,15 @@ ResolvedRenderState::ResolvedRenderState(LinearAllocator& allocator, Snapshot& s
|
||||
: transform(*snapshot.transform)
|
||||
, clipState(snapshot.mutateClipArea().serializeClip(allocator))
|
||||
, clippedBounds(clipState->rect)
|
||||
, clipSideFlags(OpClipSideFlags::Full) {}
|
||||
, clipSideFlags(OpClipSideFlags::Full)
|
||||
, localProjectionPathMask(nullptr) {}
|
||||
|
||||
ResolvedRenderState::ResolvedRenderState(const ClipRect* clipRect, const Rect& dstRect)
|
||||
: transform(Matrix4::identity())
|
||||
, clipState(clipRect)
|
||||
, clippedBounds(dstRect)
|
||||
, clipSideFlags(computeClipSideFlags(clipRect->rect, dstRect)) {
|
||||
, clipSideFlags(computeClipSideFlags(clipRect->rect, dstRect))
|
||||
, localProjectionPathMask(nullptr) {
|
||||
clippedBounds.doIntersect(clipRect->rect);
|
||||
}
|
||||
|
||||
|
||||
@@ -88,6 +88,7 @@ public:
|
||||
const ClipBase* clipState = nullptr;
|
||||
Rect clippedBounds;
|
||||
int clipSideFlags = 0;
|
||||
const SkPath* localProjectionPathMask = nullptr;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -154,7 +155,6 @@ public:
|
||||
// simple state (straight pointer/value storage):
|
||||
const float alpha;
|
||||
const RoundRectClipState* roundRectClipState;
|
||||
const ProjectionPathMask* projectionPathMask;
|
||||
const RecordedOp* op;
|
||||
|
||||
private:
|
||||
@@ -165,21 +165,18 @@ private:
|
||||
: computedState(allocator, snapshot, recordedOp, expandForStroke)
|
||||
, alpha(snapshot.alpha)
|
||||
, roundRectClipState(snapshot.roundRectClipState)
|
||||
, projectionPathMask(snapshot.projectionPathMask)
|
||||
, op(&recordedOp) {}
|
||||
|
||||
BakedOpState(LinearAllocator& allocator, Snapshot& snapshot, const ShadowOp* shadowOpPtr)
|
||||
: computedState(allocator, snapshot)
|
||||
, alpha(snapshot.alpha)
|
||||
, roundRectClipState(snapshot.roundRectClipState)
|
||||
, projectionPathMask(snapshot.projectionPathMask)
|
||||
, op(shadowOpPtr) {}
|
||||
|
||||
BakedOpState(const ClipRect* clipRect, const Rect& dstRect, const RecordedOp& recordedOp)
|
||||
: computedState(clipRect, dstRect)
|
||||
, alpha(1.0f)
|
||||
, roundRectClipState(nullptr)
|
||||
, projectionPathMask(nullptr)
|
||||
, op(&recordedOp) {}
|
||||
};
|
||||
|
||||
|
||||
@@ -389,34 +389,38 @@ void FrameBuilder::deferShadow(const RenderNodeOp& casterNodeOp) {
|
||||
}
|
||||
|
||||
void FrameBuilder::deferProjectedChildren(const RenderNode& renderNode) {
|
||||
const SkPath* projectionReceiverOutline = renderNode.properties().getOutline().getPath();
|
||||
int count = mCanvasState.save(SaveFlags::MatrixClip);
|
||||
const SkPath* projectionReceiverOutline = renderNode.properties().getOutline().getPath();
|
||||
|
||||
// can't be null, since DL=null node rejection happens before deferNodePropsAndOps
|
||||
const DisplayList& displayList = *(renderNode.getDisplayList());
|
||||
|
||||
const RecordedOp* op = (displayList.getOps()[displayList.projectionReceiveIndex]);
|
||||
const RenderNodeOp* backgroundOp = static_cast<const RenderNodeOp*>(op);
|
||||
const RenderProperties& backgroundProps = backgroundOp->renderNode->properties();
|
||||
|
||||
// Transform renderer to match background we're projecting onto
|
||||
// (by offsetting canvas by translationX/Y of background rendernode, since only those are set)
|
||||
mCanvasState.translate(backgroundProps.getTranslationX(), backgroundProps.getTranslationY());
|
||||
|
||||
// If the projection receiver has an outline, we mask projected content to it
|
||||
// (which we know, apriori, are all tessellated paths)
|
||||
mCanvasState.setProjectionPathMask(mAllocator, projectionReceiverOutline);
|
||||
|
||||
// draw projected nodes
|
||||
for (size_t i = 0; i < renderNode.mProjectedNodes.size(); i++) {
|
||||
RenderNodeOp* childOp = renderNode.mProjectedNodes[i];
|
||||
|
||||
int restoreTo = mCanvasState.save(SaveFlags::Matrix);
|
||||
mCanvasState.concatMatrix(childOp->transformFromCompositingAncestor);
|
||||
deferRenderNodeOpImpl(*childOp);
|
||||
mCanvasState.restoreToCount(restoreTo);
|
||||
SkPath transformedMaskPath; // on stack, since BakedOpState makes a deep copy
|
||||
if (projectionReceiverOutline) {
|
||||
// transform the mask for this projector into render target space
|
||||
// TODO: consider combining both transforms by stashing transform instead of applying
|
||||
SkMatrix skCurrentTransform;
|
||||
mCanvasState.currentTransform()->copyTo(skCurrentTransform);
|
||||
projectionReceiverOutline->transform(
|
||||
skCurrentTransform,
|
||||
&transformedMaskPath);
|
||||
mCanvasState.setProjectionPathMask(mAllocator, &transformedMaskPath);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < renderNode.mProjectedNodes.size(); i++) {
|
||||
RenderNodeOp* childOp = renderNode.mProjectedNodes[i];
|
||||
RenderNode& childNode = *childOp->renderNode;
|
||||
|
||||
// Draw child if it has content, but ignore state in childOp - matrix already applied to
|
||||
// transformFromCompositingAncestor, and record-time clip is ignored when projecting
|
||||
if (!childNode.nothingToDraw()) {
|
||||
int restoreTo = mCanvasState.save(SaveFlags::MatrixClip);
|
||||
|
||||
// Apply transform between ancestor and projected descendant
|
||||
mCanvasState.concatMatrix(childOp->transformFromCompositingAncestor);
|
||||
|
||||
deferNodePropsAndOps(childNode);
|
||||
|
||||
mCanvasState.restoreToCount(restoreTo);
|
||||
}
|
||||
}
|
||||
mCanvasState.restoreToCount(count);
|
||||
}
|
||||
|
||||
|
||||
@@ -140,7 +140,10 @@ public:
|
||||
// Identical round rect clip state means both ops will clip in the same way, or not at all.
|
||||
// As the state objects are const, we can compare their pointers to determine mergeability
|
||||
if (lhs->roundRectClipState != rhs->roundRectClipState) return false;
|
||||
if (lhs->projectionPathMask != rhs->projectionPathMask) return false;
|
||||
|
||||
// Local masks prevent merge, since they're potentially in different coordinate spaces
|
||||
if (lhs->computedState.localProjectionPathMask
|
||||
|| rhs->computedState.localProjectionPathMask) return false;
|
||||
|
||||
/* Clipping compatibility check
|
||||
*
|
||||
|
||||
@@ -1148,7 +1148,9 @@ bool OpenGLRenderer::storeDisplayState(DeferredDisplayState& state, int stateDef
|
||||
|
||||
// always store/restore, since these are just pointers
|
||||
state.mRoundRectClipState = currentSnapshot()->roundRectClipState;
|
||||
#if !HWUI_NEW_OPS
|
||||
state.mProjectionPathMask = currentSnapshot()->projectionPathMask;
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1156,7 +1158,9 @@ void OpenGLRenderer::restoreDisplayState(const DeferredDisplayState& state, bool
|
||||
setGlobalMatrix(state.mMatrix);
|
||||
writableSnapshot()->alpha = state.mAlpha;
|
||||
writableSnapshot()->roundRectClipState = state.mRoundRectClipState;
|
||||
#if !HWUI_NEW_OPS
|
||||
writableSnapshot()->projectionPathMask = state.mProjectionPathMask;
|
||||
#endif
|
||||
|
||||
if (state.mClipValid && !skipClipRestore) {
|
||||
writableSnapshot()->setClip(state.mClip.left, state.mClip.top,
|
||||
@@ -1833,6 +1837,7 @@ void OpenGLRenderer::drawCircle(float x, float y, float radius, const SkPaint* p
|
||||
path.addCircle(x, y, radius);
|
||||
}
|
||||
|
||||
#if !HWUI_NEW_OPS
|
||||
if (CC_UNLIKELY(currentSnapshot()->projectionPathMask != nullptr)) {
|
||||
// mask ripples with projection mask
|
||||
SkPath maskPath = *(currentSnapshot()->projectionPathMask->projectionMask);
|
||||
@@ -1852,6 +1857,7 @@ void OpenGLRenderer::drawCircle(float x, float y, float radius, const SkPaint* p
|
||||
// in local space. Note that this can create CCW paths.
|
||||
Op(path, maskPath, kIntersect_SkPathOp, &path);
|
||||
}
|
||||
#endif
|
||||
drawConvexPath(path, p);
|
||||
}
|
||||
|
||||
|
||||
@@ -146,6 +146,9 @@ void Snapshot::resetTransform(float x, float y, float z) {
|
||||
}
|
||||
|
||||
void Snapshot::buildScreenSpaceTransform(Matrix4* outTransform) const {
|
||||
#if HWUI_NEW_OPS
|
||||
LOG_ALWAYS_FATAL("not supported - not needed by new ops");
|
||||
#else
|
||||
// build (reverse ordered) list of the stack of snapshots, terminated with a NULL
|
||||
Vector<const Snapshot*> snapshotList;
|
||||
snapshotList.push(nullptr);
|
||||
@@ -171,6 +174,7 @@ void Snapshot::buildScreenSpaceTransform(Matrix4* outTransform) const {
|
||||
outTransform->multiply(*(current->transform));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
@@ -223,15 +227,19 @@ void Snapshot::setClippingRoundRect(LinearAllocator& allocator, const Rect& boun
|
||||
}
|
||||
|
||||
void Snapshot::setProjectionPathMask(LinearAllocator& allocator, const SkPath* path) {
|
||||
#if HWUI_NEW_OPS
|
||||
// TODO: remove allocator param for HWUI_NEW_OPS
|
||||
projectionPathMask = path;
|
||||
#else
|
||||
if (path) {
|
||||
ProjectionPathMask* mask = new (allocator) ProjectionPathMask;
|
||||
mask->projectionMask = path;
|
||||
buildScreenSpaceTransform(&(mask->projectionMaskTransform));
|
||||
|
||||
projectionPathMask = mask;
|
||||
} else {
|
||||
projectionPathMask = nullptr;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -63,6 +63,7 @@ public:
|
||||
float radius;
|
||||
};
|
||||
|
||||
// TODO: remove for HWUI_NEW_OPS
|
||||
class ProjectionPathMask {
|
||||
public:
|
||||
static void* operator new(size_t size) = delete;
|
||||
@@ -219,6 +220,7 @@ public:
|
||||
* Fills outTransform with the current, total transform to screen space,
|
||||
* across layer boundaries.
|
||||
*/
|
||||
// TODO: remove for HWUI_NEW_OPS
|
||||
void buildScreenSpaceTransform(Matrix4* outTransform) const;
|
||||
|
||||
/**
|
||||
@@ -294,9 +296,13 @@ public:
|
||||
const RoundRectClipState* roundRectClipState;
|
||||
|
||||
/**
|
||||
* Current projection masking path - used exclusively to mask tessellated circles.
|
||||
* Current projection masking path - used exclusively to mask projected, tessellated circles.
|
||||
*/
|
||||
#if HWUI_NEW_OPS
|
||||
const SkPath* projectionPathMask;
|
||||
#else
|
||||
const ProjectionPathMask* projectionPathMask;
|
||||
#endif
|
||||
|
||||
void dump() const;
|
||||
|
||||
|
||||
@@ -990,21 +990,26 @@ TEST(FrameBuilder, projectionReorder) {
|
||||
EXPECT_EQ(Rect(100, 100), op.unmappedBounds);
|
||||
EXPECT_EQ(SK_ColorWHITE, op.paint->getColor());
|
||||
expectedMatrix.loadIdentity();
|
||||
EXPECT_EQ(nullptr, state.computedState.localProjectionPathMask);
|
||||
break;
|
||||
case 1:
|
||||
EXPECT_EQ(Rect(-10, -10, 60, 60), op.unmappedBounds);
|
||||
EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor());
|
||||
expectedMatrix.loadTranslate(50, 50, 0); // TODO: should scroll be respected here?
|
||||
expectedMatrix.loadTranslate(50 - scrollX, 50 - scrollY, 0);
|
||||
ASSERT_NE(nullptr, state.computedState.localProjectionPathMask);
|
||||
EXPECT_EQ(Rect(-35, -30, 45, 50),
|
||||
Rect(state.computedState.localProjectionPathMask->getBounds()));
|
||||
break;
|
||||
case 2:
|
||||
EXPECT_EQ(Rect(100, 50), op.unmappedBounds);
|
||||
EXPECT_EQ(SK_ColorBLUE, op.paint->getColor());
|
||||
expectedMatrix.loadTranslate(-scrollX, 50 - scrollY, 0);
|
||||
EXPECT_EQ(nullptr, state.computedState.localProjectionPathMask);
|
||||
break;
|
||||
default:
|
||||
ADD_FAILURE();
|
||||
}
|
||||
EXPECT_MATRIX_APPROX_EQ(expectedMatrix, state.computedState.transform);
|
||||
EXPECT_EQ(expectedMatrix, state.computedState.transform);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1045,6 +1050,9 @@ TEST(FrameBuilder, projectionReorder) {
|
||||
});
|
||||
auto parent = TestUtils::createNode(0, 0, 100, 100,
|
||||
[&receiverBackground, &child](RenderProperties& properties, RecordingCanvas& canvas) {
|
||||
// Set a rect outline for the projecting ripple to be masked against.
|
||||
properties.mutableOutline().setRoundRect(10, 10, 90, 90, 5, 1.0f);
|
||||
|
||||
canvas.save(SaveFlags::MatrixClip);
|
||||
canvas.translate(-scrollX, -scrollY); // Apply scroll (note: bg undoes this internally)
|
||||
canvas.drawRenderNode(receiverBackground.get());
|
||||
@@ -1059,6 +1067,145 @@ TEST(FrameBuilder, projectionReorder) {
|
||||
EXPECT_EQ(3, renderer.getIndex());
|
||||
}
|
||||
|
||||
RENDERTHREAD_TEST(FrameBuilder, projectionHwLayer) {
|
||||
static const int scrollX = 5;
|
||||
static const int scrollY = 10;
|
||||
class ProjectionHwLayerTestRenderer : public TestRendererBase {
|
||||
public:
|
||||
void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override {
|
||||
EXPECT_EQ(0, mIndex++);
|
||||
}
|
||||
void onArcOp(const ArcOp& op, const BakedOpState& state) override {
|
||||
EXPECT_EQ(1, mIndex++);
|
||||
ASSERT_EQ(nullptr, state.computedState.localProjectionPathMask);
|
||||
}
|
||||
void endLayer() override {
|
||||
EXPECT_EQ(2, mIndex++);
|
||||
}
|
||||
void onRectOp(const RectOp& op, const BakedOpState& state) override {
|
||||
EXPECT_EQ(3, mIndex++);
|
||||
ASSERT_EQ(nullptr, state.computedState.localProjectionPathMask);
|
||||
}
|
||||
void onOvalOp(const OvalOp& op, const BakedOpState& state) override {
|
||||
EXPECT_EQ(4, mIndex++);
|
||||
ASSERT_NE(nullptr, state.computedState.localProjectionPathMask);
|
||||
Matrix4 expected;
|
||||
expected.loadTranslate(100 - scrollX, 100 - scrollY, 0);
|
||||
EXPECT_EQ(expected, state.computedState.transform);
|
||||
EXPECT_EQ(Rect(-85, -80, 295, 300),
|
||||
Rect(state.computedState.localProjectionPathMask->getBounds()));
|
||||
}
|
||||
void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
|
||||
EXPECT_EQ(5, mIndex++);
|
||||
ASSERT_EQ(nullptr, state.computedState.localProjectionPathMask);
|
||||
}
|
||||
};
|
||||
auto receiverBackground = TestUtils::createNode(0, 0, 400, 400,
|
||||
[](RenderProperties& properties, RecordingCanvas& canvas) {
|
||||
properties.setProjectionReceiver(true);
|
||||
// scroll doesn't apply to background, so undone via translationX/Y
|
||||
// NOTE: translationX/Y only! no other transform properties may be set for a proj receiver!
|
||||
properties.setTranslationX(scrollX);
|
||||
properties.setTranslationY(scrollY);
|
||||
|
||||
canvas.drawRect(0, 0, 400, 400, SkPaint());
|
||||
});
|
||||
auto projectingRipple = TestUtils::createNode(0, 0, 200, 200,
|
||||
[](RenderProperties& properties, RecordingCanvas& canvas) {
|
||||
properties.setProjectBackwards(true);
|
||||
properties.setClipToBounds(false);
|
||||
canvas.drawOval(100, 100, 300, 300, SkPaint()); // drawn mostly out of layer bounds
|
||||
});
|
||||
auto child = TestUtils::createNode(100, 100, 300, 300,
|
||||
[&projectingRipple](RenderProperties& properties, RecordingCanvas& canvas) {
|
||||
properties.mutateLayerProperties().setType(LayerType::RenderLayer);
|
||||
canvas.drawRenderNode(projectingRipple.get());
|
||||
canvas.drawArc(0, 0, 200, 200, 0.0f, 280.0f, true, SkPaint());
|
||||
});
|
||||
auto parent = TestUtils::createNode(0, 0, 400, 400,
|
||||
[&receiverBackground, &child](RenderProperties& properties, RecordingCanvas& canvas) {
|
||||
// Set a rect outline for the projecting ripple to be masked against.
|
||||
properties.mutableOutline().setRoundRect(10, 10, 390, 390, 0, 1.0f);
|
||||
canvas.translate(-scrollX, -scrollY); // Apply scroll (note: bg undoes this internally)
|
||||
canvas.drawRenderNode(receiverBackground.get());
|
||||
canvas.drawRenderNode(child.get());
|
||||
});
|
||||
|
||||
OffscreenBuffer** layerHandle = child->getLayerHandle();
|
||||
|
||||
// create RenderNode's layer here in same way prepareTree would, setting windowTransform
|
||||
OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 200, 200);
|
||||
Matrix4 windowTransform;
|
||||
windowTransform.loadTranslate(100, 100, 0); // total transform of layer's origin
|
||||
layer.setWindowTransform(windowTransform);
|
||||
*layerHandle = &layer;
|
||||
|
||||
auto syncedList = TestUtils::createSyncedNodeList(parent);
|
||||
LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
|
||||
layerUpdateQueue.enqueueLayerWithDamage(child.get(), Rect(200, 200));
|
||||
FrameBuilder frameBuilder(layerUpdateQueue, SkRect::MakeWH(400, 400), 400, 400,
|
||||
syncedList, sLightGeometry, nullptr);
|
||||
ProjectionHwLayerTestRenderer renderer;
|
||||
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
|
||||
EXPECT_EQ(6, renderer.getIndex());
|
||||
|
||||
// clean up layer pointer, so we can safely destruct RenderNode
|
||||
*layerHandle = nullptr;
|
||||
}
|
||||
|
||||
RENDERTHREAD_TEST(FrameBuilder, projectionChildScroll) {
|
||||
static const int scrollX = 500000;
|
||||
static const int scrollY = 0;
|
||||
class ProjectionChildScrollTestRenderer : public TestRendererBase {
|
||||
public:
|
||||
void onRectOp(const RectOp& op, const BakedOpState& state) override {
|
||||
EXPECT_EQ(0, mIndex++);
|
||||
EXPECT_TRUE(state.computedState.transform.isIdentity());
|
||||
}
|
||||
void onOvalOp(const OvalOp& op, const BakedOpState& state) override {
|
||||
EXPECT_EQ(1, mIndex++);
|
||||
ASSERT_NE(nullptr, state.computedState.clipState);
|
||||
ASSERT_EQ(ClipMode::Rectangle, state.computedState.clipState->mode);
|
||||
ASSERT_EQ(Rect(400, 400), state.computedState.clipState->rect);
|
||||
EXPECT_TRUE(state.computedState.transform.isIdentity());
|
||||
}
|
||||
};
|
||||
auto receiverBackground = TestUtils::createNode(0, 0, 400, 400,
|
||||
[](RenderProperties& properties, RecordingCanvas& canvas) {
|
||||
properties.setProjectionReceiver(true);
|
||||
canvas.drawRect(0, 0, 400, 400, SkPaint());
|
||||
});
|
||||
auto projectingRipple = TestUtils::createNode(0, 0, 200, 200,
|
||||
[](RenderProperties& properties, RecordingCanvas& canvas) {
|
||||
// scroll doesn't apply to background, so undone via translationX/Y
|
||||
// NOTE: translationX/Y only! no other transform properties may be set for a proj receiver!
|
||||
properties.setTranslationX(scrollX);
|
||||
properties.setTranslationY(scrollY);
|
||||
properties.setProjectBackwards(true);
|
||||
properties.setClipToBounds(false);
|
||||
canvas.drawOval(0, 0, 200, 200, SkPaint());
|
||||
});
|
||||
auto child = TestUtils::createNode(0, 0, 400, 400,
|
||||
[&projectingRipple](RenderProperties& properties, RecordingCanvas& canvas) {
|
||||
// Record time clip will be ignored by projectee
|
||||
canvas.clipRect(100, 100, 300, 300, SkRegion::kIntersect_Op);
|
||||
|
||||
canvas.translate(-scrollX, -scrollY); // Apply scroll (note: bg undoes this internally)
|
||||
canvas.drawRenderNode(projectingRipple.get());
|
||||
});
|
||||
auto parent = TestUtils::createNode(0, 0, 400, 400,
|
||||
[&receiverBackground, &child](RenderProperties& properties, RecordingCanvas& canvas) {
|
||||
canvas.drawRenderNode(receiverBackground.get());
|
||||
canvas.drawRenderNode(child.get());
|
||||
});
|
||||
|
||||
FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(400, 400), 400, 400,
|
||||
TestUtils::createSyncedNodeList(parent), sLightGeometry, nullptr);
|
||||
ProjectionChildScrollTestRenderer renderer;
|
||||
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
|
||||
EXPECT_EQ(2, renderer.getIndex());
|
||||
}
|
||||
|
||||
// creates a 100x100 shadow casting node with provided translationZ
|
||||
static sp<RenderNode> createWhiteRectShadowCaster(float translationZ) {
|
||||
return TestUtils::createNode(0, 0, 100, 100,
|
||||
|
||||
@@ -3,24 +3,32 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<FrameLayout
|
||||
<ScrollView
|
||||
android:orientation="vertical"
|
||||
android:translationX="50dp"
|
||||
android:translationY="50dp"
|
||||
android:elevation="30dp"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="200dp"
|
||||
android:background="@drawable/round_rect_background">
|
||||
<View
|
||||
android:id="@+id/clickable1"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="100dp"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"/>
|
||||
<View
|
||||
android:id="@+id/clickable2"
|
||||
android:translationX="50dp"
|
||||
android:translationY="10dp"
|
||||
android:layout_width="150dp"
|
||||
android:layout_height="100dp"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"/>
|
||||
</FrameLayout>
|
||||
<FrameLayout
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="wrap_content">
|
||||
<View
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="2000dp"/>
|
||||
<View
|
||||
android:id="@+id/clickable1"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="100dp"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"/>
|
||||
<View
|
||||
android:id="@+id/clickable2"
|
||||
android:translationX="50dp"
|
||||
android:translationY="10dp"
|
||||
android:layout_width="150dp"
|
||||
android:layout_height="100dp"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"/>
|
||||
</FrameLayout>
|
||||
</ScrollView>
|
||||
</LinearLayout>
|
||||
|
||||
Reference in New Issue
Block a user