Interactive Bevel Sample App

BUG=skia:
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2246243002

Review-Url: https://codereview.chromium.org/2246243002
diff --git a/samplecode/SampleBevel.cpp b/samplecode/SampleBevel.cpp
new file mode 100644
index 0000000..868ce35
--- /dev/null
+++ b/samplecode/SampleBevel.cpp
@@ -0,0 +1,306 @@
+/*
+ * 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 "SkCanvas.h"
+#include "SkLightingShader.h"
+#include "SkNormalSource.h"
+#include "sk_tool_utils.h"
+
+
+class BevelView : public SampleView {
+public:
+    BevelView()
+        : fShapeBounds(SkRect::MakeWH(kShapeBoundsSize, kShapeBoundsSize))
+        , fRedLight(SkLights::Light::MakeDirectional(SkColor3f::Make(0.6f, 0.45f, 0.3f),
+                                                     SkVector3::Make(0.0f, -5.0f, 1.0f)))
+        , fBlueLight(SkLights::Light::MakeDirectional(SkColor3f::Make(0.3f, 0.45f, 0.6f),
+                                                      SkVector3::Make(0.0f, 5.0f, 1.0f))) {
+        this->setBGColor(0xFF666868); // Slightly colorized gray for contrast
+
+        // Lights
+        SkLights::Builder builder;
+        builder.add(fRedLight);
+        builder.add(fBlueLight);
+        builder.add(SkLights::Light::MakeAmbient(SkColor3f::Make(0.4f, 0.4f, 0.4f)));
+        fLights = builder.finish();
+
+        // Controls
+
+        SkScalar currY = kSliderHeight;
+
+        const SkScalar kWidthCtrlInitialPos = 0.2f;
+        fCtrlRangeRects[0] = SkRect::MakeXYWH(0.0f, currY,
+                                              kCtrlRange + kSliderWidth,
+                                              kSliderHeight);
+        fWidthCtrlRect = SkRect::MakeXYWH(kWidthCtrlInitialPos * kCtrlRange, currY,
+                                          kSliderWidth, kSliderHeight);
+        fBevelWidth = kBevelWidthMax * kWidthCtrlInitialPos;
+        currY += 2 * kSliderHeight;
+
+        const SkScalar kHeightCtrlInitialPos = 0.75f;
+        fCtrlRangeRects[1] = SkRect::MakeXYWH(0.0f, currY,
+                                              kCtrlRange + kSliderWidth,
+                                              kSliderHeight);
+        fHeightCtrlRect = SkRect::MakeXYWH(kHeightCtrlInitialPos * kCtrlRange, currY,
+                                           kSliderWidth, kSliderHeight);
+        // Mapping from (0, 1) to (-1, 1)
+        fBevelHeight = kBevelHeightMax * (kHeightCtrlInitialPos * 2.0f - 1.0f);
+        currY += 2 * kSliderHeight;
+
+        const SkScalar kTypeCtrlInitialPos = 1.0f / (2.0f * kBevelTypeCount);
+        fCtrlRangeRects[2] = SkRect::MakeXYWH(0.0f, currY,
+                                              kCtrlRange + kSliderWidth,
+                                              kSliderHeight);
+        fTypeCtrlRect = SkRect::MakeXYWH(kTypeCtrlInitialPos * kCtrlRange, currY,
+                                         kSliderWidth, kSliderHeight);
+        fBevelType = (SkNormalSource::BevelType) SkScalarFloorToInt(kTypeCtrlInitialPos);
+        currY += 2 * kSliderHeight;
+
+        fSelectedCtrlRect = nullptr;
+        fDirtyNormalSource = true;
+
+        fLabelTypeface = sk_tool_utils::create_portable_typeface("sans-serif", SkFontStyle());
+    }
+
+protected:
+    bool onQuery(SkEvent *evt) override {
+        if (SampleCode::TitleQ(*evt)) {
+            SampleCode::TitleR(evt, "Bevel");
+            return true;
+        }
+
+        return this->INHERITED::onQuery(evt);
+    }
+
+    enum Shape {
+        kCircle_Shape,
+        kRect_Shape,
+    };
+    void drawShape(enum Shape shape, SkCanvas* canvas) {
+        canvas->save();
+
+        SkPaint paint;
+
+        if (fDirtyNormalSource) {
+            fNormalSource = SkNormalSource::MakeBevel(fBevelType, fBevelWidth, fBevelHeight);
+            fDirtyNormalSource = false;
+        }
+
+        paint.setShader(SkLightingShader::Make(nullptr, fNormalSource, fLights));
+        paint.setAntiAlias(true);
+        paint.setColor(0xFFDDDDDD);
+        switch (shape) {
+            case kCircle_Shape:
+                canvas->drawCircle(fShapeBounds.centerX(), fShapeBounds.centerY(),
+                                   fShapeBounds.width()/2.0f, paint);
+                break;
+            case kRect_Shape:
+                canvas->drawRect(fShapeBounds, paint);
+                break;
+            default:
+                SkDEBUGFAIL("Invalid shape enum for drawShape");
+        }
+
+        canvas->restore();
+    }
+
+    void onDrawContent(SkCanvas *canvas) override {
+
+        canvas->save();
+        canvas->resetMatrix(); // Force static controls and labels
+
+        // Draw controls
+
+        SkPaint ctrlRectPaint;
+        ctrlRectPaint.setColor(0xFFF3F3F3);
+        canvas->drawRect(fWidthCtrlRect, ctrlRectPaint);
+        canvas->drawRect(fHeightCtrlRect, ctrlRectPaint);
+        canvas->drawRect(fTypeCtrlRect, ctrlRectPaint);
+
+        SkPaint ctrlRectRangePaint;
+        ctrlRectRangePaint.setColor(0xFFFFFFFF);
+        ctrlRectRangePaint.setStyle(SkPaint::kStroke_Style);
+        ctrlRectRangePaint.setStrokeWidth(2.0f);
+
+        for (size_t i = 0; i < kNumControls; i++) {
+            canvas->drawRect(fCtrlRangeRects[i], ctrlRectRangePaint);
+        }
+
+        // Draw labels
+        constexpr SkScalar kTextSize = 12.0f;
+        SkString widthLabel, heightLabel, typeLabel;
+        SkPaint labelPaint;
+        labelPaint.setTypeface(fLabelTypeface);
+        labelPaint.setAntiAlias(true);
+        labelPaint.setColor(0xFFFFFFFF);
+        labelPaint.setTextSize(kTextSize);
+
+        widthLabel.appendf("BevelWidth: %f", fBevelWidth);
+        heightLabel.appendf("BevelHeight: %f", fBevelHeight);
+        typeLabel.append("BevelType: ");
+
+        switch (fBevelType) {
+            case SkNormalSource::BevelType::kLinear:
+                typeLabel.append("Linear");
+                break;
+            case SkNormalSource::BevelType::kRoundedIn:
+                typeLabel.append("RoundedIn");
+                break;
+            case SkNormalSource::BevelType::kRoundedOut:
+                typeLabel.append("RoundedOut");
+                break;
+        }
+
+        canvas->drawText(widthLabel.c_str(), widthLabel.size(), 0,
+                         fWidthCtrlRect.fTop - kTextSize/2.0f, labelPaint);
+        canvas->drawText(heightLabel.c_str(), heightLabel.size(), 0,
+                         fHeightCtrlRect.fTop - kTextSize/2.0f, labelPaint);
+        canvas->drawText(typeLabel.c_str(), typeLabel.size(), 0,
+                         fTypeCtrlRect.fTop - kTextSize/2.0f, labelPaint);
+
+        canvas->restore(); // Return to modified matrix when drawing shapes
+
+        // Draw shapes
+        SkScalar xPos = kCtrlRange + 25.0f;
+        SkScalar yPos = fShapeBounds.height();
+        for (Shape shape : { kCircle_Shape, kRect_Shape }) {
+            canvas->save();
+            canvas->translate(xPos, yPos);
+            this->drawShape(shape, canvas);
+            canvas->restore();
+
+            xPos += 1.2f * fShapeBounds.width();
+        }
+    }
+
+    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;
+
+        // Control deselection
+        if (Click::State::kUp_State == click->fState) {
+            fSelectedCtrlRect = nullptr;
+            return true;
+        }
+
+        // Control selection
+        if (nullptr == fSelectedCtrlRect && Click::State::kDown_State == click->fState) {
+            if (fWidthCtrlRect.contains(SkRect::MakeXYWH(x, y, 1, 1))) {
+                fSelectedCtrlRect = &fWidthCtrlRect;
+            } else if (fHeightCtrlRect.contains(SkRect::MakeXYWH(x, y, 1, 1))) {
+                fSelectedCtrlRect = &fHeightCtrlRect;
+            } else if (fTypeCtrlRect.contains(SkRect::MakeXYWH(x, y, 1, 1))) {
+                fSelectedCtrlRect = &fTypeCtrlRect;
+            }
+        }
+
+        if (nullptr != fSelectedCtrlRect) { // Control modification
+            fSelectedCtrlRect->offsetTo(SkScalarPin(x, 0.0f, kCtrlRange), fSelectedCtrlRect->fTop);
+
+            fBevelHeight = (fHeightCtrlRect.fLeft / kCtrlRange) * kBevelHeightMax * 2.0f
+                           - kBevelHeightMax;
+            fBevelWidth = (fWidthCtrlRect.fLeft / kCtrlRange) * kBevelWidthMax;
+            fBevelType = (SkNormalSource::BevelType)SkTMin(
+                    SkScalarFloorToInt(kBevelTypeCount * fTypeCtrlRect.fLeft / kCtrlRange),
+                    kBevelTypeCount - 1);
+
+            // Snap type controls to 3 positions
+            fTypeCtrlRect.offsetTo(kCtrlRange * ( ((int)fBevelType)/SkIntToScalar(kBevelTypeCount)
+                                                  + 1.0f/(2.0f * kBevelTypeCount) ),
+                                   fTypeCtrlRect.fTop);
+
+            // Ensuring width is non-zero
+            fBevelWidth = SkMaxScalar(1.0f, fBevelWidth);
+
+            fDirtyNormalSource = true;
+
+            this->inval(nullptr);
+            return true;
+        } else { // Moving light
+            if (dx != 0 || dy != 0) {
+                float recipX = 1.0f / kAppWidth;
+                float recipY = 1.0f / kAppHeight;
+
+                if (0 == click->fModifierKeys) { // No modifier
+                    fBlueLight = SkLights::Light::MakeDirectional(fBlueLight.color(),
+                            SkVector3::Make((kAppWidth/2.0f - x) * recipX * -3.0f,
+                                            (kAppHeight/2.0f - y) * recipY * -3.0f,
+                                            1.0f));
+                } else if (1 == click->fModifierKeys) { // Shift key
+                    fRedLight = SkLights::Light::MakeDirectional(fRedLight.color(),
+                            SkVector3::Make((kAppWidth/2.0f - x) * recipX * -3.0f,
+                                            (kAppHeight/2.0f - y) * recipY * -3.0f,
+                                            1.0f));
+                }
+
+                SkLights::Builder builder;
+                builder.add(fRedLight);
+                builder.add(fBlueLight);
+                builder.add(SkLights::Light::MakeAmbient(
+                        SkColor3f::Make(0.4f, 0.4f, 0.4f)));
+                fLights = builder.finish();
+
+                this->inval(nullptr);
+            }
+            return true;
+        }
+
+        return true;
+    }
+
+private:
+    static constexpr int kNumTestRects = 3;
+
+    static constexpr SkScalar kAppWidth = 400.0f;
+    static constexpr SkScalar kAppHeight = 400.0f;
+    static constexpr SkScalar kShapeBoundsSize = 120.0f;
+
+    static constexpr SkScalar kCtrlRange = 150.0f;
+    static constexpr SkScalar kBevelWidthMax = kShapeBoundsSize;
+    static constexpr SkScalar kBevelHeightMax = 50.0f;
+    static constexpr int      kBevelTypeCount = 3;
+
+    static constexpr SkScalar kSliderHeight = 20.0f;
+    static constexpr SkScalar kSliderWidth = 10.0f;
+
+    const SkRect fShapeBounds;
+
+    static constexpr int kNumControls = 3;
+    SkRect fCtrlRangeRects[kNumControls];
+    SkRect* fSelectedCtrlRect;
+    SkRect fWidthCtrlRect;
+    SkRect fHeightCtrlRect;
+    SkRect fTypeCtrlRect;
+
+    SkScalar fBevelWidth;
+    SkScalar fBevelHeight;
+    SkNormalSource::BevelType fBevelType;
+    sk_sp<SkNormalSource> fNormalSource;
+    bool fDirtyNormalSource;
+
+    sk_sp<SkLights> fLights;
+    SkLights::Light fRedLight;
+    SkLights::Light fBlueLight;
+
+    sk_sp<SkTypeface> fLabelTypeface;
+
+    typedef SampleView INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+static SkView* MyFactory() { return new BevelView; }
+static SkViewRegister reg(MyFactory);
+