2016-08-12 17:40:38 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Copyright 2016 Google Inc.
|
|
|
|
*
|
|
|
|
* Use of this source code is governed by a BSD-style license that can be
|
|
|
|
* found in the LICENSE file.
|
|
|
|
*/
|
|
|
|
#include "SampleCode.h"
|
|
|
|
#include "SkBlurMask.h"
|
|
|
|
#include "SkBlurMaskFilter.h"
|
|
|
|
#include "SkCanvas.h"
|
2016-08-17 14:59:41 +00:00
|
|
|
#include "SkGaussianEdgeShader.h"
|
2016-08-12 17:40:38 +00:00
|
|
|
#include "SkPath.h"
|
|
|
|
#include "SkPoint3.h"
|
|
|
|
#include "SkUtils.h"
|
|
|
|
#include "SkView.h"
|
|
|
|
#include "sk_tool_utils.h"
|
|
|
|
|
2016-08-17 14:59:41 +00:00
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
2016-08-12 17:40:38 +00:00
|
|
|
class ShadowsView : public SampleView {
|
|
|
|
SkPath fRectPath;
|
|
|
|
SkPath fRRPath;
|
|
|
|
SkPath fCirclePath;
|
|
|
|
SkPoint3 fLightPos;
|
|
|
|
|
|
|
|
bool fShowAmbient;
|
|
|
|
bool fShowSpot;
|
2016-08-17 17:06:18 +00:00
|
|
|
bool fUseAlt;
|
2016-08-12 17:40:38 +00:00
|
|
|
bool fShowObject;
|
|
|
|
|
|
|
|
public:
|
|
|
|
ShadowsView()
|
|
|
|
: fShowAmbient(true)
|
|
|
|
, fShowSpot(true)
|
2016-08-17 17:06:18 +00:00
|
|
|
, fUseAlt(true)
|
2016-08-12 17:40:38 +00:00
|
|
|
, fShowObject(true) {}
|
|
|
|
|
|
|
|
protected:
|
|
|
|
void onOnceBeforeDraw() override {
|
|
|
|
fCirclePath.addCircle(0, 0, 50);
|
|
|
|
fRectPath.addRect(SkRect::MakeXYWH(-100, -50, 200, 100));
|
|
|
|
fRRPath.addRRect(SkRRect::MakeRectXY(SkRect::MakeXYWH(-100, -50, 200, 100), 4, 4));
|
2016-08-17 17:06:18 +00:00
|
|
|
fLightPos = SkPoint3::Make(-2, -2, 6);
|
2016-08-12 17:40:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// overrides from SkEventSink
|
|
|
|
bool onQuery(SkEvent* evt) override {
|
|
|
|
if (SampleCode::TitleQ(*evt)) {
|
|
|
|
SampleCode::TitleR(evt, "AndroidShadows");
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
SkUnichar uni;
|
|
|
|
if (SampleCode::CharQ(*evt, &uni)) {
|
|
|
|
switch (uni) {
|
|
|
|
case 'B':
|
|
|
|
fShowAmbient = !fShowAmbient;
|
|
|
|
break;
|
|
|
|
case 'S':
|
|
|
|
fShowSpot = !fShowSpot;
|
|
|
|
break;
|
2016-08-17 17:06:18 +00:00
|
|
|
case 'T':
|
|
|
|
fUseAlt = !fUseAlt;
|
|
|
|
break;
|
2016-08-12 17:40:38 +00:00
|
|
|
case 'O':
|
|
|
|
fShowObject = !fShowObject;
|
|
|
|
break;
|
|
|
|
case '>':
|
|
|
|
fLightPos.fZ += 10;
|
|
|
|
break;
|
|
|
|
case '<':
|
|
|
|
fLightPos.fZ -= 10;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
this->inval(nullptr);
|
|
|
|
}
|
|
|
|
return this->INHERITED::onQuery(evt);
|
|
|
|
}
|
|
|
|
|
|
|
|
void drawBG(SkCanvas* canvas) {
|
|
|
|
canvas->drawColor(0xFFDDDDDD);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void GetOcclRect(const SkPath& path, SkRect* occlRect) {
|
|
|
|
SkRect pathRect;
|
|
|
|
SkRRect pathRRect;
|
|
|
|
if (path.isOval(&pathRect)) {
|
|
|
|
*occlRect = sk_tool_utils::compute_central_occluder(SkRRect::MakeOval(pathRect));
|
|
|
|
} else if (path.isRRect(&pathRRect)) {
|
|
|
|
*occlRect = sk_tool_utils::compute_central_occluder(pathRRect);
|
|
|
|
} else if (path.isRect(occlRect)) {
|
|
|
|
// the inverse transform for the spot shadow occluder doesn't always get us
|
|
|
|
// back to exactly the same position, so deducting a little slop
|
|
|
|
occlRect->inset(1, 1);
|
|
|
|
} else {
|
|
|
|
*occlRect = SkRect::MakeEmpty();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void drawAmbientShadow(SkCanvas* canvas, const SkPath& path, SkScalar zValue,
|
|
|
|
SkScalar ambientAlpha) {
|
|
|
|
|
|
|
|
if (ambientAlpha <= 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const SkScalar kHeightFactor = 1.f / 128.f;
|
|
|
|
const SkScalar kGeomFactor = 64;
|
|
|
|
|
|
|
|
SkScalar umbraAlpha = 1 / (1 + SkMaxScalar(zValue*kHeightFactor, 0));
|
|
|
|
SkScalar radius = zValue*kHeightFactor*kGeomFactor;
|
|
|
|
|
|
|
|
// occlude blur
|
|
|
|
SkRect occlRect;
|
|
|
|
GetOcclRect(path, &occlRect);
|
|
|
|
sk_sp<SkMaskFilter> mf = SkBlurMaskFilter::Make(kNormal_SkBlurStyle,
|
|
|
|
SkBlurMask::ConvertRadiusToSigma(radius),
|
|
|
|
occlRect,
|
|
|
|
SkBlurMaskFilter::kNone_BlurFlag);
|
|
|
|
|
|
|
|
SkPaint paint;
|
|
|
|
paint.setAntiAlias(true);
|
|
|
|
paint.setMaskFilter(std::move(mf));
|
|
|
|
paint.setColor(SkColorSetARGB((unsigned char)(ambientAlpha*umbraAlpha*255.999f), 0, 0, 0));
|
|
|
|
canvas->drawPath(path, paint);
|
|
|
|
|
|
|
|
// draw occlusion rect
|
2016-08-17 14:59:41 +00:00
|
|
|
#if DRAW_OCCL_RECT
|
2016-08-12 17:40:38 +00:00
|
|
|
SkPaint stroke;
|
|
|
|
stroke.setStyle(SkPaint::kStroke_Style);
|
|
|
|
stroke.setColor(SK_ColorBLUE);
|
|
|
|
canvas->drawRect(occlRect, stroke);
|
2016-08-17 14:59:41 +00:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
void drawAmbientShadowAlt(SkCanvas* canvas, const SkPath& path, SkScalar zValue,
|
|
|
|
SkScalar ambientAlpha) {
|
|
|
|
|
|
|
|
if (ambientAlpha <= 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-08-17 17:06:18 +00:00
|
|
|
SkRect pathRect;
|
|
|
|
SkRRect pathRRect;
|
|
|
|
if ((!path.isOval(&pathRect) || pathRect.width() != pathRect.height()) &&
|
|
|
|
(!path.isRRect(&pathRRect) || !pathRRect.allCornersCircular()) &&
|
|
|
|
!path.isRect(&pathRect)) {
|
|
|
|
this->drawAmbientShadow(canvas, path, zValue, ambientAlpha);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-08-17 14:59:41 +00:00
|
|
|
const SkScalar kHeightFactor = 1.f / 128.f;
|
|
|
|
const SkScalar kGeomFactor = 64;
|
|
|
|
|
|
|
|
SkScalar umbraAlpha = 1 / (1 + SkMaxScalar(zValue*kHeightFactor, 0));
|
|
|
|
SkScalar radius = zValue*kHeightFactor*kGeomFactor;
|
|
|
|
|
2016-08-17 17:06:18 +00:00
|
|
|
// For all of these, we outset the rect by the radius to get our coverage shape.
|
|
|
|
if (path.isOval(nullptr)) {
|
|
|
|
pathRect.outset(radius, radius);
|
|
|
|
pathRRect = SkRRect::MakeOval(pathRect);
|
|
|
|
} else if (path.isRect(nullptr)) {
|
|
|
|
pathRect.outset(radius, radius);
|
|
|
|
pathRRect = SkRRect::MakeRectXY(pathRect, radius, radius);
|
2016-08-17 14:59:41 +00:00
|
|
|
} else {
|
2016-08-17 17:06:18 +00:00
|
|
|
pathRRect.outset(radius, radius);
|
2016-08-17 14:59:41 +00:00
|
|
|
}
|
|
|
|
|
2016-08-17 17:06:18 +00:00
|
|
|
SkPaint paint;
|
|
|
|
paint.setAntiAlias(true);
|
|
|
|
// handle scale of radius due to CTM
|
|
|
|
SkScalar maxScale = canvas->getTotalMatrix().getMaxScale();
|
|
|
|
radius *= maxScale;
|
|
|
|
unsigned char gray = (unsigned char)(ambientAlpha*umbraAlpha*255.999f);
|
|
|
|
SkASSERT(radius < 256);
|
|
|
|
// convert the radius to fixed point 8.8 and
|
|
|
|
// place it in the G,B components of the color
|
|
|
|
unsigned char intPart = (unsigned char)radius;
|
|
|
|
SkScalar fracPart = radius - intPart;
|
|
|
|
paint.setColor(SkColorSetARGB(1, gray, intPart, (unsigned char)(fracPart*256.f)));
|
|
|
|
|
2016-08-17 18:33:29 +00:00
|
|
|
sk_sp<SkShader> gaussShader = SkGaussianEdgeShader::Make();
|
2016-08-17 17:06:18 +00:00
|
|
|
paint.setShader(gaussShader);
|
|
|
|
canvas->drawRRect(pathRRect, paint);
|
2016-08-12 17:40:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void drawSpotShadow(SkCanvas* canvas, const SkPath& path, SkScalar zValue,
|
|
|
|
SkPoint3 lightPos, SkScalar lightWidth, SkScalar spotAlpha) {
|
|
|
|
if (spotAlpha <= 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
SkScalar zRatio = zValue / (lightPos.fZ - zValue);
|
|
|
|
if (zRatio < 0.0f) {
|
|
|
|
zRatio = 0.0f;
|
|
|
|
} else if (zRatio > 0.95f) {
|
|
|
|
zRatio = 0.95f;
|
|
|
|
}
|
|
|
|
SkScalar radius = lightWidth*zRatio;
|
|
|
|
|
|
|
|
// compute the transformation params
|
|
|
|
SkPoint center = SkPoint::Make(path.getBounds().centerX(), path.getBounds().centerY());
|
|
|
|
canvas->getTotalMatrix().mapPoints(¢er, 1);
|
|
|
|
SkPoint offset = SkPoint::Make(-zRatio*(lightPos.fX - center.fX),
|
|
|
|
-zRatio*(lightPos.fY - center.fY));
|
|
|
|
SkScalar scale = lightPos.fZ / (lightPos.fZ - zValue);
|
|
|
|
if (scale < 1.0f) {
|
|
|
|
scale = 1.0f;
|
|
|
|
} else if (scale > 1024.f) {
|
|
|
|
scale = 1024.f;
|
|
|
|
}
|
|
|
|
|
|
|
|
SkAutoCanvasRestore acr(canvas, true);
|
|
|
|
|
|
|
|
SkRect occlRect;
|
|
|
|
GetOcclRect(path, &occlRect);
|
|
|
|
// apply inverse transform
|
|
|
|
occlRect.offset(-offset);
|
2016-08-17 17:06:18 +00:00
|
|
|
#if 0
|
|
|
|
// It looks like the scale may be invalid
|
|
|
|
SkScalar scale = lightPos.fZ / (lightPos.fZ - zValue);
|
|
|
|
if (scale < 1.0f) {
|
|
|
|
scale = 1.0f;
|
|
|
|
} else if (scale > 1024.f) {
|
|
|
|
scale = 1024.f;
|
|
|
|
}
|
2016-08-12 17:40:38 +00:00
|
|
|
occlRect.fLeft /= scale;
|
|
|
|
occlRect.fRight /= scale;
|
|
|
|
occlRect.fTop /= scale;
|
|
|
|
occlRect.fBottom /= scale;
|
2016-08-17 17:06:18 +00:00
|
|
|
#endif
|
2016-08-12 17:40:38 +00:00
|
|
|
sk_sp<SkMaskFilter> mf = SkBlurMaskFilter::Make(kNormal_SkBlurStyle,
|
|
|
|
SkBlurMask::ConvertRadiusToSigma(radius),
|
|
|
|
occlRect,
|
|
|
|
SkBlurMaskFilter::kNone_BlurFlag);
|
|
|
|
|
|
|
|
SkPaint paint;
|
|
|
|
paint.setAntiAlias(true);
|
|
|
|
paint.setMaskFilter(std::move(mf));
|
|
|
|
paint.setColor(SkColorSetARGB((unsigned char)(spotAlpha*255.999f), 0, 0, 0));
|
|
|
|
|
|
|
|
// apply transformation to shadow
|
|
|
|
canvas->translate(offset.fX, offset.fY);
|
2016-08-17 17:06:18 +00:00
|
|
|
#if 0
|
|
|
|
// It looks like the scale may be invalid
|
2016-08-12 17:40:38 +00:00
|
|
|
canvas->scale(scale, scale);
|
2016-08-17 17:06:18 +00:00
|
|
|
#endif
|
2016-08-12 17:40:38 +00:00
|
|
|
canvas->drawPath(path, paint);
|
|
|
|
|
|
|
|
// draw occlusion rect
|
2016-08-17 14:59:41 +00:00
|
|
|
#if DRAW_OCCL_RECT
|
2016-08-12 17:40:38 +00:00
|
|
|
SkPaint stroke;
|
|
|
|
stroke.setStyle(SkPaint::kStroke_Style);
|
|
|
|
stroke.setColor(SK_ColorRED);
|
2016-08-17 14:59:41 +00:00
|
|
|
canvas->drawRect(occlRect, stroke)
|
|
|
|
#endif
|
2016-08-12 17:40:38 +00:00
|
|
|
}
|
|
|
|
|
2016-08-17 17:06:18 +00:00
|
|
|
void drawSpotShadowAlt(SkCanvas* canvas, const SkPath& path, SkScalar zValue,
|
|
|
|
SkPoint3 lightPos, SkScalar lightWidth, SkScalar spotAlpha) {
|
|
|
|
if (spotAlpha <= 0) {
|
|
|
|
return;
|
|
|
|
}
|
2016-08-12 17:40:38 +00:00
|
|
|
|
2016-08-17 17:06:18 +00:00
|
|
|
SkRect pathRect;
|
|
|
|
SkRRect pathRRect;
|
|
|
|
if ((!path.isOval(&pathRect) || pathRect.width() != pathRect.height()) &&
|
|
|
|
(!path.isRRect(&pathRRect) || !pathRRect.allCornersCircular()) &&
|
|
|
|
!path.isRect(&pathRect)) {
|
|
|
|
this->drawSpotShadow(canvas, path, zValue, lightPos, lightWidth, spotAlpha);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
SkScalar zRatio = zValue / (lightPos.fZ - zValue);
|
|
|
|
if (zRatio < 0.0f) {
|
|
|
|
zRatio = 0.0f;
|
|
|
|
} else if (zRatio > 0.95f) {
|
|
|
|
zRatio = 0.95f;
|
|
|
|
}
|
|
|
|
SkScalar radius = lightWidth*zRatio;
|
|
|
|
|
|
|
|
// For all of these, we outset the rect by the radius to get our coverage shape.
|
|
|
|
if (path.isOval(nullptr)) {
|
|
|
|
pathRect.outset(radius, radius);
|
|
|
|
pathRRect = SkRRect::MakeOval(pathRect);
|
|
|
|
} else if (path.isRect(nullptr)) {
|
|
|
|
pathRect.outset(radius, radius);
|
|
|
|
pathRRect = SkRRect::MakeRectXY(pathRect, radius, radius);
|
|
|
|
} else {
|
|
|
|
pathRRect.outset(radius, radius);
|
|
|
|
}
|
|
|
|
|
|
|
|
// compute the transformation params
|
|
|
|
SkPoint center = SkPoint::Make(path.getBounds().centerX(), path.getBounds().centerY());
|
|
|
|
canvas->getTotalMatrix().mapPoints(¢er, 1);
|
|
|
|
SkPoint offset = SkPoint::Make(-zRatio*(lightPos.fX - center.fX),
|
|
|
|
-zRatio*(lightPos.fY - center.fY));
|
|
|
|
SkAutoCanvasRestore acr(canvas, true);
|
|
|
|
|
|
|
|
SkPaint paint;
|
|
|
|
paint.setAntiAlias(true);
|
2016-08-17 18:33:29 +00:00
|
|
|
sk_sp<SkShader> gaussShader = SkGaussianEdgeShader::Make();
|
2016-08-17 17:06:18 +00:00
|
|
|
paint.setShader(gaussShader);
|
|
|
|
// handle scale of radius due to CTM
|
|
|
|
SkScalar maxScale = canvas->getTotalMatrix().getMaxScale();
|
|
|
|
radius *= maxScale;
|
|
|
|
unsigned char gray = (unsigned char)(spotAlpha*255.999f);
|
|
|
|
SkASSERT(radius < 256);
|
|
|
|
// convert the radius to fixed point 8.8 and
|
|
|
|
// place it in the G,B components of the color
|
|
|
|
unsigned char intPart = (unsigned char)radius;
|
|
|
|
SkScalar fracPart = radius - intPart;
|
|
|
|
paint.setColor(SkColorSetARGB(1, gray, intPart, (unsigned char)(fracPart*256.f)));
|
|
|
|
|
|
|
|
// apply transformation to shadow
|
|
|
|
canvas->translate(offset.fX, offset.fY);
|
|
|
|
#if 0
|
|
|
|
// It looks like the scale may be invalid
|
|
|
|
SkScalar scale = lightPos.fZ / (lightPos.fZ - zValue);
|
|
|
|
if (scale < 1.0f) {
|
|
|
|
scale = 1.0f;
|
|
|
|
} else if (scale > 1024.f) {
|
|
|
|
scale = 1024.f;
|
|
|
|
}
|
|
|
|
canvas->scale(scale, scale);
|
|
|
|
#endif
|
|
|
|
canvas->drawRRect(pathRRect, paint);
|
|
|
|
}
|
|
|
|
|
|
|
|
void drawShadowedPath(SkCanvas* canvas, const SkPath& path, SkScalar zValue,
|
|
|
|
const SkPaint& paint, SkScalar ambientAlpha,
|
|
|
|
const SkPoint3& lightPos, SkScalar lightWidth, SkScalar spotAlpha) {
|
2016-08-12 17:40:38 +00:00
|
|
|
if (fShowAmbient) {
|
2016-08-17 17:06:18 +00:00
|
|
|
if (fUseAlt) {
|
|
|
|
this->drawAmbientShadowAlt(canvas, path, zValue, ambientAlpha);
|
2016-08-17 14:59:41 +00:00
|
|
|
} else {
|
2016-08-17 17:06:18 +00:00
|
|
|
this->drawAmbientShadow(canvas, path, zValue, ambientAlpha);
|
2016-08-17 14:59:41 +00:00
|
|
|
}
|
2016-08-12 17:40:38 +00:00
|
|
|
}
|
|
|
|
if (fShowSpot) {
|
2016-08-17 17:06:18 +00:00
|
|
|
if (fUseAlt) {
|
|
|
|
this->drawSpotShadowAlt(canvas, path, zValue, lightPos, lightWidth, spotAlpha);
|
|
|
|
} else {
|
|
|
|
this->drawSpotShadow(canvas, path, zValue, lightPos, lightWidth, spotAlpha);
|
|
|
|
}
|
2016-08-12 17:40:38 +00:00
|
|
|
}
|
|
|
|
if (fShowObject) {
|
|
|
|
canvas->drawPath(path, paint);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void onDrawContent(SkCanvas* canvas) override {
|
|
|
|
this->drawBG(canvas);
|
2016-08-17 17:06:18 +00:00
|
|
|
const SkScalar kLightWidth = 3;
|
|
|
|
const SkScalar kAmbientAlpha = 0.25f;
|
|
|
|
const SkScalar kSpotAlpha = 0.25f;
|
2016-08-12 17:40:38 +00:00
|
|
|
|
|
|
|
SkPaint paint;
|
|
|
|
paint.setAntiAlias(true);
|
|
|
|
|
2016-08-17 17:06:18 +00:00
|
|
|
SkPoint3 lightPos = fLightPos;
|
|
|
|
|
2016-08-12 17:40:38 +00:00
|
|
|
paint.setColor(SK_ColorWHITE);
|
|
|
|
canvas->translate(200, 90);
|
2016-08-17 17:06:18 +00:00
|
|
|
lightPos.fX += 200;
|
|
|
|
lightPos.fY += 90;
|
|
|
|
this->drawShadowedPath(canvas, fRectPath, 5, paint, kAmbientAlpha,
|
|
|
|
lightPos, kLightWidth, kSpotAlpha);
|
2016-08-12 17:40:38 +00:00
|
|
|
|
|
|
|
paint.setColor(SK_ColorRED);
|
|
|
|
canvas->translate(250, 0);
|
2016-08-17 17:06:18 +00:00
|
|
|
lightPos.fX += 250;
|
|
|
|
this->drawShadowedPath(canvas, fRRPath, 5, paint, kAmbientAlpha,
|
|
|
|
lightPos, kLightWidth, kSpotAlpha);
|
2016-08-12 17:40:38 +00:00
|
|
|
|
|
|
|
paint.setColor(SK_ColorBLUE);
|
|
|
|
canvas->translate(-250, 110);
|
2016-08-17 17:06:18 +00:00
|
|
|
lightPos.fX -= 250;
|
|
|
|
lightPos.fY += 110;
|
|
|
|
this->drawShadowedPath(canvas, fCirclePath, 5, paint, 0.0f,
|
|
|
|
lightPos, kLightWidth, 0.5f);
|
2016-08-17 14:59:41 +00:00
|
|
|
|
|
|
|
paint.setColor(SK_ColorGREEN);
|
|
|
|
canvas->translate(250, 0);
|
2016-08-17 17:06:18 +00:00
|
|
|
lightPos.fX += 250;
|
|
|
|
this->drawShadowedPath(canvas, fRRPath, 5, paint, kAmbientAlpha,
|
|
|
|
lightPos, kLightWidth, kSpotAlpha);
|
2016-08-12 17:40:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
protected:
|
|
|
|
SkView::Click* onFindClickHandler(SkScalar x, SkScalar y, unsigned modi) override {
|
|
|
|
return new SkView::Click(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool onClick(Click *click) override {
|
|
|
|
SkScalar x = click->fCurr.fX;
|
|
|
|
SkScalar y = click->fCurr.fY;
|
|
|
|
|
|
|
|
SkScalar dx = x - click->fPrev.fX;
|
|
|
|
SkScalar dy = y - click->fPrev.fY;
|
|
|
|
|
|
|
|
if (dx != 0 || dy != 0) {
|
|
|
|
fLightPos.fX += dx;
|
|
|
|
fLightPos.fY += dy;
|
|
|
|
this->inval(nullptr);
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
typedef SkView INHERITED;
|
|
|
|
};
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
static SkView* MyFactory() { return new ShadowsView; }
|
|
|
|
static SkViewRegister reg(MyFactory);
|