| /* | |
| * Copyright 2012 Google Inc. | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include "sk_tool_utils.h" | |
| #include "SampleCode.h" | |
| #include "SkView.h" | |
| #include "SkCanvas.h" | |
| #include "SkGeometry.h" | |
| #include "SkPathMeasure.h" | |
| #include "SkRandom.h" | |
| #include "SkRRect.h" | |
| #include "SkColorPriv.h" | |
| #include "SkStrokerPriv.h" | |
| #include "SkSurface.h" | |
| static bool hittest(const SkPoint& target, SkScalar x, SkScalar y) { | |
| const SkScalar TOL = 7; | |
| return SkPoint::Distance(target, SkPoint::Make(x, y)) <= TOL; | |
| } | |
| static int getOnCurvePoints(const SkPath& path, SkPoint storage[]) { | |
| SkPath::RawIter iter(path); | |
| SkPoint pts[4]; | |
| SkPath::Verb verb; | |
| int count = 0; | |
| while ((verb = iter.next(pts)) != SkPath::kDone_Verb) { | |
| switch (verb) { | |
| case SkPath::kMove_Verb: | |
| case SkPath::kLine_Verb: | |
| case SkPath::kQuad_Verb: | |
| case SkPath::kConic_Verb: | |
| case SkPath::kCubic_Verb: | |
| storage[count++] = pts[0]; | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| return count; | |
| } | |
| static void getContourCounts(const SkPath& path, SkTArray<int>* contourCounts) { | |
| SkPath::RawIter iter(path); | |
| SkPoint pts[4]; | |
| SkPath::Verb verb; | |
| int count = 0; | |
| while ((verb = iter.next(pts)) != SkPath::kDone_Verb) { | |
| switch (verb) { | |
| case SkPath::kMove_Verb: | |
| case SkPath::kLine_Verb: | |
| count += 1; | |
| break; | |
| case SkPath::kQuad_Verb: | |
| case SkPath::kConic_Verb: | |
| count += 2; | |
| break; | |
| case SkPath::kCubic_Verb: | |
| count += 3; | |
| break; | |
| case SkPath::kClose_Verb: | |
| contourCounts->push_back(count); | |
| count = 0; | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| if (count > 0) { | |
| contourCounts->push_back(count); | |
| } | |
| } | |
| static void erase(const sk_sp<SkSurface>& surface) { | |
| SkCanvas* canvas = surface->getCanvas(); | |
| if (canvas) { | |
| canvas->clear(SK_ColorTRANSPARENT); | |
| } | |
| } | |
| struct StrokeTypeButton { | |
| SkRect fBounds; | |
| char fLabel; | |
| bool fEnabled; | |
| }; | |
| struct CircleTypeButton : public StrokeTypeButton { | |
| bool fFill; | |
| }; | |
| class QuadStrokerView : public SampleView { | |
| enum { | |
| SKELETON_COLOR = 0xFF0000FF, | |
| WIREFRAME_COLOR = 0x80FF0000 | |
| }; | |
| enum { | |
| kCount = 18 | |
| }; | |
| SkPoint fPts[kCount]; | |
| SkRect fWeightControl; | |
| SkRect fRadiusControl; | |
| SkRect fErrorControl; | |
| SkRect fWidthControl; | |
| SkRect fBounds; | |
| SkMatrix fMatrix, fInverse; | |
| sk_sp<SkShader> fShader; | |
| sk_sp<SkSurface> fMinSurface; | |
| sk_sp<SkSurface> fMaxSurface; | |
| StrokeTypeButton fCubicButton; | |
| StrokeTypeButton fConicButton; | |
| StrokeTypeButton fQuadButton; | |
| StrokeTypeButton fArcButton; | |
| StrokeTypeButton fRRectButton; | |
| CircleTypeButton fCircleButton; | |
| StrokeTypeButton fTextButton; | |
| SkString fText; | |
| SkScalar fTextSize; | |
| SkScalar fWeight; | |
| SkScalar fRadius; | |
| SkScalar fWidth, fDWidth; | |
| SkScalar fWidthScale; | |
| int fW, fH, fZoom; | |
| bool fAnimate; | |
| bool fDrawRibs; | |
| bool fDrawTangents; | |
| bool fDrawTDivs; | |
| #ifdef SK_DEBUG | |
| #define kStrokerErrorMin 0.001f | |
| #define kStrokerErrorMax 5 | |
| #endif | |
| #define kWidthMin 1 | |
| #define kWidthMax 100 | |
| public: | |
| QuadStrokerView() { | |
| this->setBGColor(SK_ColorLTGRAY); | |
| fPts[0].set(50, 200); // cubic | |
| fPts[1].set(50, 100); | |
| fPts[2].set(150, 50); | |
| fPts[3].set(300, 50); | |
| fPts[4].set(350, 200); // conic | |
| fPts[5].set(350, 100); | |
| fPts[6].set(450, 50); | |
| fPts[7].set(150, 300); // quad | |
| fPts[8].set(150, 200); | |
| fPts[9].set(250, 150); | |
| fPts[10].set(250, 200); // arc | |
| fPts[11].set(250, 300); | |
| fPts[12].set(150, 350); | |
| fPts[13].set(200, 200); // rrect | |
| fPts[14].set(400, 400); | |
| fPts[15].set(250, 250); // oval | |
| fPts[16].set(450, 450); | |
| fText = "a"; | |
| fTextSize = 12; | |
| fWidth = 50; | |
| fDWidth = 0.25f; | |
| fWeight = 1; | |
| fRadius = 150; | |
| fCubicButton.fLabel = 'C'; | |
| fCubicButton.fEnabled = false; | |
| fConicButton.fLabel = 'K'; | |
| fConicButton.fEnabled = false; | |
| fQuadButton.fLabel = 'Q'; | |
| fQuadButton.fEnabled = false; | |
| fArcButton.fLabel = 'A'; | |
| fArcButton.fEnabled = true; | |
| fRRectButton.fLabel = 'R'; | |
| fRRectButton.fEnabled = false; | |
| fCircleButton.fLabel = 'O'; | |
| fCircleButton.fEnabled = true; | |
| fCircleButton.fFill = true; | |
| fTextButton.fLabel = 'T'; | |
| fTextButton.fEnabled = false; | |
| fAnimate = false; | |
| setAsNeeded(); | |
| } | |
| protected: | |
| bool onQuery(SkEvent* evt) override { | |
| if (SampleCode::TitleQ(*evt)) { | |
| SampleCode::TitleR(evt, "QuadStroker"); | |
| return true; | |
| } | |
| SkUnichar uni; | |
| if (fTextButton.fEnabled && SampleCode::CharQ(*evt, &uni)) { | |
| switch (uni) { | |
| case ' ': | |
| fText = ""; | |
| break; | |
| case '-': | |
| fTextSize = SkTMax(1.0f, fTextSize - 1); | |
| break; | |
| case '+': | |
| case '=': | |
| fTextSize += 1; | |
| break; | |
| default: | |
| fText.appendUnichar(uni); | |
| } | |
| this->inval(nullptr); | |
| return true; | |
| } | |
| return this->INHERITED::onQuery(evt); | |
| } | |
| void onSizeChange() override { | |
| fRadiusControl.setXYWH(this->width() - 200, 30, 30, 400); | |
| fWeightControl.setXYWH(this->width() - 150, 30, 30, 400); | |
| fErrorControl.setXYWH(this->width() - 100, 30, 30, 400); | |
| fWidthControl.setXYWH(this->width() - 50, 30, 30, 400); | |
| int buttonOffset = 450; | |
| fCubicButton.fBounds.setXYWH(this->width() - 50, SkIntToScalar(buttonOffset), 30, 30); | |
| buttonOffset += 50; | |
| fConicButton.fBounds.setXYWH(this->width() - 50, SkIntToScalar(buttonOffset), 30, 30); | |
| buttonOffset += 50; | |
| fQuadButton.fBounds.setXYWH(this->width() - 50, SkIntToScalar(buttonOffset), 30, 30); | |
| buttonOffset += 50; | |
| fArcButton.fBounds.setXYWH(this->width() - 50, SkIntToScalar(buttonOffset), 30, 30); | |
| buttonOffset += 50; | |
| fRRectButton.fBounds.setXYWH(this->width() - 50, SkIntToScalar(buttonOffset), 30, 30); | |
| buttonOffset += 50; | |
| fCircleButton.fBounds.setXYWH(this->width() - 50, SkIntToScalar(buttonOffset), 30, 30); | |
| buttonOffset += 50; | |
| fTextButton.fBounds.setXYWH(this->width() - 50, SkIntToScalar(buttonOffset), 30, 30); | |
| this->INHERITED::onSizeChange(); | |
| } | |
| void copyMinToMax() { | |
| erase(fMaxSurface); | |
| SkCanvas* canvas = fMaxSurface->getCanvas(); | |
| canvas->save(); | |
| canvas->concat(fMatrix); | |
| fMinSurface->draw(canvas, 0, 0, nullptr); | |
| canvas->restore(); | |
| SkPaint paint; | |
| paint.setXfermodeMode(SkXfermode::kClear_Mode); | |
| for (int iy = 1; iy < fH; ++iy) { | |
| SkScalar y = SkIntToScalar(iy * fZoom); | |
| canvas->drawLine(0, y - SK_ScalarHalf, 999, y - SK_ScalarHalf, paint); | |
| } | |
| for (int ix = 1; ix < fW; ++ix) { | |
| SkScalar x = SkIntToScalar(ix * fZoom); | |
| canvas->drawLine(x - SK_ScalarHalf, 0, x - SK_ScalarHalf, 999, paint); | |
| } | |
| } | |
| void setWHZ(int width, int height, int zoom) { | |
| fZoom = zoom; | |
| fBounds.set(0, 0, SkIntToScalar(width * zoom), SkIntToScalar(height * zoom)); | |
| fMatrix.setScale(SkIntToScalar(zoom), SkIntToScalar(zoom)); | |
| fInverse.setScale(SK_Scalar1 / zoom, SK_Scalar1 / zoom); | |
| fShader = sk_tool_utils::create_checkerboard_shader(0xFFCCCCCC, 0xFFFFFFFF, zoom); | |
| SkImageInfo info = SkImageInfo::MakeN32Premul(width, height); | |
| fMinSurface = SkSurface::MakeRaster(info); | |
| info = info.makeWH(width * zoom, height * zoom); | |
| fMaxSurface = SkSurface::MakeRaster(info); | |
| } | |
| void draw_points(SkCanvas* canvas, const SkPath& path, SkColor color, | |
| bool show_lines) { | |
| SkPaint paint; | |
| paint.setColor(color); | |
| paint.setAlpha(0x80); | |
| paint.setAntiAlias(true); | |
| int n = path.countPoints(); | |
| SkAutoSTArray<32, SkPoint> pts(n); | |
| if (show_lines && fDrawTangents) { | |
| SkTArray<int> contourCounts; | |
| getContourCounts(path, &contourCounts); | |
| SkPoint* ptPtr = pts.get(); | |
| for (int i = 0; i < contourCounts.count(); ++i) { | |
| int count = contourCounts[i]; | |
| path.getPoints(ptPtr, count); | |
| canvas->drawPoints(SkCanvas::kPolygon_PointMode, count, ptPtr, paint); | |
| ptPtr += count; | |
| } | |
| } else { | |
| n = getOnCurvePoints(path, pts.get()); | |
| } | |
| paint.setStrokeWidth(5); | |
| canvas->drawPoints(SkCanvas::kPoints_PointMode, n, pts.get(), paint); | |
| } | |
| void draw_ribs(SkCanvas* canvas, const SkPath& path, SkScalar width, | |
| SkColor color) { | |
| const SkScalar radius = width / 2; | |
| SkPathMeasure meas(path, false); | |
| SkScalar total = meas.getLength(); | |
| SkScalar delta = 8; | |
| SkPaint paint, labelP; | |
| paint.setColor(color); | |
| labelP.setColor(color & 0xff5f9f5f); | |
| SkPoint pos, tan; | |
| int index = 0; | |
| for (SkScalar dist = 0; dist <= total; dist += delta) { | |
| if (meas.getPosTan(dist, &pos, &tan)) { | |
| tan.scale(radius); | |
| tan.rotateCCW(); | |
| canvas->drawLine(pos.x() + tan.x(), pos.y() + tan.y(), | |
| pos.x() - tan.x(), pos.y() - tan.y(), paint); | |
| if (0 == index % 10) { | |
| SkString label; | |
| label.appendS32(index); | |
| SkRect dot = SkRect::MakeXYWH(pos.x() - 2, pos.y() - 2, 4, 4); | |
| canvas->drawRect(dot, labelP); | |
| canvas->drawText(label.c_str(), label.size(), | |
| pos.x() - tan.x() * 1.25f, pos.y() - tan.y() * 1.25f, labelP); | |
| } | |
| } | |
| ++index; | |
| } | |
| } | |
| void draw_t_divs(SkCanvas* canvas, const SkPath& path, SkScalar width, SkColor color) { | |
| const SkScalar radius = width / 2; | |
| SkPaint paint; | |
| paint.setColor(color); | |
| SkPathMeasure meas(path, false); | |
| SkScalar total = meas.getLength(); | |
| SkScalar delta = 8; | |
| int ribs = 0; | |
| for (SkScalar dist = 0; dist <= total; dist += delta) { | |
| ++ribs; | |
| } | |
| SkPath::RawIter iter(path); | |
| SkPoint pts[4]; | |
| if (SkPath::kMove_Verb != iter.next(pts)) { | |
| SkASSERT(0); | |
| return; | |
| } | |
| SkPath::Verb verb = iter.next(pts); | |
| SkASSERT(SkPath::kLine_Verb <= verb && verb <= SkPath::kCubic_Verb); | |
| SkPoint pos, tan; | |
| for (int index = 0; index < ribs; ++index) { | |
| SkScalar t = (SkScalar) index / ribs; | |
| switch (verb) { | |
| case SkPath::kLine_Verb: | |
| tan = pts[1] - pts[0]; | |
| pos = pts[0]; | |
| pos.fX += tan.fX * t; | |
| pos.fY += tan.fY * t; | |
| break; | |
| case SkPath::kQuad_Verb: | |
| pos = SkEvalQuadAt(pts, t); | |
| tan = SkEvalQuadTangentAt(pts, t); | |
| break; | |
| case SkPath::kConic_Verb: { | |
| SkConic conic(pts, iter.conicWeight()); | |
| pos = conic.evalAt(t); | |
| tan = conic.evalTangentAt(t); | |
| } break; | |
| case SkPath::kCubic_Verb: | |
| SkEvalCubicAt(pts, t, &pos, &tan, nullptr); | |
| break; | |
| default: | |
| SkASSERT(0); | |
| return; | |
| } | |
| tan.setLength(radius); | |
| tan.rotateCCW(); | |
| canvas->drawLine(pos.x() + tan.x(), pos.y() + tan.y(), | |
| pos.x() - tan.x(), pos.y() - tan.y(), paint); | |
| if (0 == index % 10) { | |
| SkString label; | |
| label.appendS32(index); | |
| canvas->drawText(label.c_str(), label.size(), | |
| pos.x() + tan.x() * 1.25f, pos.y() + tan.y() * 1.25f, paint); | |
| } | |
| } | |
| } | |
| void draw_stroke(SkCanvas* canvas, const SkPath& path, SkScalar width, SkScalar scale, | |
| bool drawText) { | |
| if (path.isEmpty()) { | |
| return; | |
| } | |
| SkRect bounds = path.getBounds(); | |
| this->setWHZ(SkScalarCeilToInt(bounds.right()), drawText | |
| ? SkScalarRoundToInt(scale * 3 / 2) : SkScalarRoundToInt(scale), | |
| SkScalarRoundToInt(950.0f / scale)); | |
| erase(fMinSurface); | |
| SkPaint paint; | |
| paint.setColor(0x1f1f0f0f); | |
| paint.setStyle(SkPaint::kStroke_Style); | |
| paint.setStrokeWidth(width * scale * scale); | |
| paint.setColor(0x3f0f1f3f); | |
| if (drawText) { | |
| fMinSurface->getCanvas()->drawPath(path, paint); | |
| this->copyMinToMax(); | |
| fMaxSurface->draw(canvas, 0, 0, nullptr); | |
| } | |
| paint.setAntiAlias(true); | |
| paint.setStyle(SkPaint::kStroke_Style); | |
| paint.setStrokeWidth(1); | |
| paint.setColor(SKELETON_COLOR); | |
| SkPath scaled; | |
| SkMatrix matrix; | |
| matrix.reset(); | |
| matrix.setScale(950 / scale, 950 / scale); | |
| if (drawText) { | |
| path.transform(matrix, &scaled); | |
| } else { | |
| scaled = path; | |
| } | |
| canvas->drawPath(scaled, paint); | |
| draw_points(canvas, scaled, SKELETON_COLOR, true); | |
| if (fDrawRibs) { | |
| draw_ribs(canvas, scaled, width, 0xFF00FF00); | |
| } | |
| if (fDrawTDivs) { | |
| draw_t_divs(canvas, scaled, width, 0xFF3F3F00); | |
| } | |
| SkPath fill; | |
| SkPaint p; | |
| p.setStyle(SkPaint::kStroke_Style); | |
| if (drawText) { | |
| p.setStrokeWidth(width * scale * scale); | |
| } else { | |
| p.setStrokeWidth(width); | |
| } | |
| p.getFillPath(path, &fill); | |
| SkPath scaledFill; | |
| if (drawText) { | |
| fill.transform(matrix, &scaledFill); | |
| } else { | |
| scaledFill = fill; | |
| } | |
| paint.setColor(WIREFRAME_COLOR); | |
| canvas->drawPath(scaledFill, paint); | |
| draw_points(canvas, scaledFill, WIREFRAME_COLOR, false); | |
| } | |
| void draw_fill(SkCanvas* canvas, const SkRect& rect, SkScalar width) { | |
| if (rect.isEmpty()) { | |
| return; | |
| } | |
| SkPaint paint; | |
| paint.setColor(0x1f1f0f0f); | |
| paint.setStyle(SkPaint::kStroke_Style); | |
| paint.setStrokeWidth(width); | |
| SkPath path; | |
| SkScalar maxSide = SkTMax(rect.width(), rect.height()) / 2; | |
| SkPoint center = { rect.fLeft + maxSide, rect.fTop + maxSide }; | |
| path.addCircle(center.fX, center.fY, maxSide); | |
| canvas->drawPath(path, paint); | |
| paint.setStyle(SkPaint::kFill_Style); | |
| path.reset(); | |
| path.addCircle(center.fX, center.fY, maxSide - width / 2); | |
| paint.setColor(0x3f0f1f3f); | |
| canvas->drawPath(path, paint); | |
| path.reset(); | |
| path.setFillType(SkPath::kEvenOdd_FillType); | |
| path.addCircle(center.fX, center.fY, maxSide + width / 2); | |
| SkRect outside = SkRect::MakeXYWH(center.fX - maxSide - width, center.fY - maxSide - width, | |
| (maxSide + width) * 2, (maxSide + width) * 2); | |
| path.addRect(outside); | |
| canvas->drawPath(path, paint); | |
| } | |
| void draw_button(SkCanvas* canvas, const StrokeTypeButton& button) { | |
| SkPaint paint; | |
| paint.setAntiAlias(true); | |
| paint.setStyle(SkPaint::kStroke_Style); | |
| paint.setColor(button.fEnabled ? 0xFF3F0000 : 0x6F3F0000); | |
| canvas->drawRect(button.fBounds, paint); | |
| paint.setTextSize(25.0f); | |
| paint.setColor(button.fEnabled ? 0xFF3F0000 : 0x6F3F0000); | |
| paint.setTextAlign(SkPaint::kCenter_Align); | |
| paint.setStyle(SkPaint::kFill_Style); | |
| canvas->drawText(&button.fLabel, 1, button.fBounds.centerX(), button.fBounds.fBottom - 5, | |
| paint); | |
| } | |
| void draw_control(SkCanvas* canvas, const SkRect& bounds, SkScalar value, | |
| SkScalar min, SkScalar max, const char* name) { | |
| SkPaint paint; | |
| paint.setAntiAlias(true); | |
| paint.setStyle(SkPaint::kStroke_Style); | |
| canvas->drawRect(bounds, paint); | |
| SkScalar scale = max - min; | |
| SkScalar yPos = bounds.fTop + (value - min) * bounds.height() / scale; | |
| paint.setColor(0xFFFF0000); | |
| canvas->drawLine(bounds.fLeft - 5, yPos, bounds.fRight + 5, yPos, paint); | |
| SkString label; | |
| label.printf("%0.3g", value); | |
| paint.setColor(0xFF000000); | |
| paint.setTextSize(11.0f); | |
| paint.setStyle(SkPaint::kFill_Style); | |
| canvas->drawText(label.c_str(), label.size(), bounds.fLeft + 5, yPos - 5, paint); | |
| paint.setTextSize(13.0f); | |
| canvas->drawText(name, strlen(name), bounds.fLeft, bounds.bottom() + 11, paint); | |
| } | |
| void setForGeometry() { | |
| fDrawRibs = true; | |
| fDrawTangents = true; | |
| fDrawTDivs = false; | |
| fWidthScale = 1; | |
| } | |
| void setForText() { | |
| fDrawRibs = fDrawTangents = fDrawTDivs = false; | |
| fWidthScale = 0.002f; | |
| } | |
| void setForSingles() { | |
| setForGeometry(); | |
| fDrawTDivs = true; | |
| } | |
| void setAsNeeded() { | |
| if (fConicButton.fEnabled || fCubicButton.fEnabled || fQuadButton.fEnabled) { | |
| setForSingles(); | |
| } else if (fRRectButton.fEnabled || fCircleButton.fEnabled || fArcButton.fEnabled) { | |
| setForGeometry(); | |
| } else { | |
| setForText(); | |
| } | |
| } | |
| bool arcCenter(SkPoint* center) { | |
| SkPath path; | |
| path.moveTo(fPts[10]); | |
| path.arcTo(fPts[11], fPts[12], fRadius); | |
| SkPath::Iter iter(path, false); | |
| SkPoint pts[4]; | |
| iter.next(pts); | |
| if (SkPath::kLine_Verb == iter.next(pts)) { | |
| iter.next(pts); | |
| } | |
| SkVector before = pts[0] - pts[1]; | |
| SkVector after = pts[1] - pts[2]; | |
| before.setLength(fRadius); | |
| after.setLength(fRadius); | |
| SkVector beforeCCW, afterCCW; | |
| before.rotateCCW(&beforeCCW); | |
| after.rotateCCW(&afterCCW); | |
| beforeCCW += pts[0]; | |
| afterCCW += pts[2]; | |
| *center = beforeCCW; | |
| if (SkScalarNearlyEqual(beforeCCW.fX, afterCCW.fX) | |
| && SkScalarNearlyEqual(beforeCCW.fY, afterCCW.fY)) { | |
| return true; | |
| } | |
| SkVector beforeCW, afterCW; | |
| before.rotateCW(&beforeCW); | |
| after.rotateCW(&afterCW); | |
| beforeCW += pts[0]; | |
| afterCW += pts[2]; | |
| *center = beforeCW; | |
| return SkScalarNearlyEqual(beforeCW.fX, afterCW.fX) | |
| && SkScalarNearlyEqual(beforeCCW.fY, afterCW.fY); | |
| } | |
| void onDrawContent(SkCanvas* canvas) override { | |
| SkPath path; | |
| SkScalar width = fWidth; | |
| if (fCubicButton.fEnabled) { | |
| path.moveTo(fPts[0]); | |
| path.cubicTo(fPts[1], fPts[2], fPts[3]); | |
| setForSingles(); | |
| draw_stroke(canvas, path, width, 950, false); | |
| } | |
| if (fConicButton.fEnabled) { | |
| path.reset(); | |
| path.moveTo(fPts[4]); | |
| path.conicTo(fPts[5], fPts[6], fWeight); | |
| setForSingles(); | |
| draw_stroke(canvas, path, width, 950, false); | |
| } | |
| if (fQuadButton.fEnabled) { | |
| path.reset(); | |
| path.moveTo(fPts[7]); | |
| path.quadTo(fPts[8], fPts[9]); | |
| setForSingles(); | |
| draw_stroke(canvas, path, width, 950, false); | |
| } | |
| if (fArcButton.fEnabled) { | |
| path.reset(); | |
| path.moveTo(fPts[10]); | |
| path.arcTo(fPts[11], fPts[12], fRadius); | |
| setForGeometry(); | |
| draw_stroke(canvas, path, width, 950, false); | |
| SkPath pathPts; | |
| pathPts.moveTo(fPts[10]); | |
| pathPts.lineTo(fPts[11]); | |
| pathPts.lineTo(fPts[12]); | |
| draw_points(canvas, pathPts, SK_ColorDKGRAY, true); | |
| } | |
| if (fRRectButton.fEnabled) { | |
| SkScalar rad = 32; | |
| SkRect r; | |
| r.set(&fPts[13], 2); | |
| path.reset(); | |
| SkRRect rr; | |
| rr.setRectXY(r, rad, rad); | |
| path.addRRect(rr); | |
| setForGeometry(); | |
| draw_stroke(canvas, path, width, 950, false); | |
| path.reset(); | |
| SkRRect rr2; | |
| rr.inset(width/2, width/2, &rr2); | |
| path.addRRect(rr2, SkPath::kCCW_Direction); | |
| rr.inset(-width/2, -width/2, &rr2); | |
| path.addRRect(rr2, SkPath::kCW_Direction); | |
| SkPaint paint; | |
| paint.setAntiAlias(true); | |
| paint.setColor(0x40FF8844); | |
| canvas->drawPath(path, paint); | |
| } | |
| if (fCircleButton.fEnabled) { | |
| path.reset(); | |
| SkRect r; | |
| r.set(&fPts[15], 2); | |
| path.addOval(r); | |
| setForGeometry(); | |
| if (fCircleButton.fFill) { | |
| if (fArcButton.fEnabled) { | |
| SkPoint center; | |
| if (arcCenter(¢er)) { | |
| r.set(center.fX - fRadius, center.fY - fRadius, center.fX + fRadius, | |
| center.fY + fRadius); | |
| } | |
| } | |
| draw_fill(canvas, r, width); | |
| } else { | |
| draw_stroke(canvas, path, width, 950, false); | |
| } | |
| } | |
| if (fTextButton.fEnabled) { | |
| path.reset(); | |
| SkPaint paint; | |
| paint.setAntiAlias(true); | |
| paint.setTextSize(fTextSize); | |
| paint.getTextPath(fText.c_str(), fText.size(), 0, fTextSize, &path); | |
| setForText(); | |
| draw_stroke(canvas, path, width * fWidthScale / fTextSize, fTextSize, true); | |
| } | |
| if (fAnimate) { | |
| fWidth += fDWidth; | |
| if (fDWidth > 0 && fWidth > kWidthMax) { | |
| fDWidth = -fDWidth; | |
| } else if (fDWidth < 0 && fWidth < kWidthMin) { | |
| fDWidth = -fDWidth; | |
| } | |
| } | |
| setAsNeeded(); | |
| if (fConicButton.fEnabled) { | |
| draw_control(canvas, fWeightControl, fWeight, 0, 5, "weight"); | |
| } | |
| if (fArcButton.fEnabled) { | |
| draw_control(canvas, fRadiusControl, fRadius, 0, 500, "radius"); | |
| } | |
| #ifdef SK_DEBUG | |
| draw_control(canvas, fErrorControl, gDebugStrokerError, kStrokerErrorMin, kStrokerErrorMax, | |
| "error"); | |
| #endif | |
| draw_control(canvas, fWidthControl, fWidth * fWidthScale, kWidthMin * fWidthScale, | |
| kWidthMax * fWidthScale, "width"); | |
| draw_button(canvas, fQuadButton); | |
| draw_button(canvas, fCubicButton); | |
| draw_button(canvas, fConicButton); | |
| draw_button(canvas, fArcButton); | |
| draw_button(canvas, fRRectButton); | |
| draw_button(canvas, fCircleButton); | |
| draw_button(canvas, fTextButton); | |
| this->inval(nullptr); | |
| } | |
| class MyClick : public Click { | |
| public: | |
| int fIndex; | |
| MyClick(SkView* target, int index) : Click(target), fIndex(index) {} | |
| }; | |
| virtual SkView::Click* onFindClickHandler(SkScalar x, SkScalar y, | |
| unsigned modi) override { | |
| for (size_t i = 0; i < SK_ARRAY_COUNT(fPts); ++i) { | |
| if (hittest(fPts[i], x, y)) { | |
| return new MyClick(this, (int)i); | |
| } | |
| } | |
| const SkRect& rectPt = SkRect::MakeXYWH(x, y, 1, 1); | |
| if (fWeightControl.contains(rectPt)) { | |
| return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 1); | |
| } | |
| if (fRadiusControl.contains(rectPt)) { | |
| return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 2); | |
| } | |
| #ifdef SK_DEBUG | |
| if (fErrorControl.contains(rectPt)) { | |
| return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 3); | |
| } | |
| #endif | |
| if (fWidthControl.contains(rectPt)) { | |
| return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 4); | |
| } | |
| if (fCubicButton.fBounds.contains(rectPt)) { | |
| fCubicButton.fEnabled ^= true; | |
| return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 5); | |
| } | |
| if (fConicButton.fBounds.contains(rectPt)) { | |
| fConicButton.fEnabled ^= true; | |
| return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 6); | |
| } | |
| if (fQuadButton.fBounds.contains(rectPt)) { | |
| fQuadButton.fEnabled ^= true; | |
| return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 7); | |
| } | |
| if (fArcButton.fBounds.contains(rectPt)) { | |
| fArcButton.fEnabled ^= true; | |
| return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 8); | |
| } | |
| if (fRRectButton.fBounds.contains(rectPt)) { | |
| fRRectButton.fEnabled ^= true; | |
| return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 9); | |
| } | |
| if (fCircleButton.fBounds.contains(rectPt)) { | |
| bool wasEnabled = fCircleButton.fEnabled; | |
| fCircleButton.fEnabled = !fCircleButton.fFill; | |
| fCircleButton.fFill = wasEnabled && !fCircleButton.fFill; | |
| return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 10); | |
| } | |
| if (fTextButton.fBounds.contains(rectPt)) { | |
| fTextButton.fEnabled ^= true; | |
| return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 11); | |
| } | |
| return this->INHERITED::onFindClickHandler(x, y, modi); | |
| } | |
| static SkScalar MapScreenYtoValue(int y, const SkRect& control, SkScalar min, | |
| SkScalar max) { | |
| return (SkIntToScalar(y) - control.fTop) / control.height() * (max - min) + min; | |
| } | |
| bool onClick(Click* click) override { | |
| int index = ((MyClick*)click)->fIndex; | |
| if (index < (int) SK_ARRAY_COUNT(fPts)) { | |
| fPts[index].offset(SkIntToScalar(click->fICurr.fX - click->fIPrev.fX), | |
| SkIntToScalar(click->fICurr.fY - click->fIPrev.fY)); | |
| this->inval(nullptr); | |
| } else if (index == (int) SK_ARRAY_COUNT(fPts) + 1) { | |
| fWeight = MapScreenYtoValue(click->fICurr.fY, fWeightControl, 0, 5); | |
| } else if (index == (int) SK_ARRAY_COUNT(fPts) + 2) { | |
| fRadius = MapScreenYtoValue(click->fICurr.fY, fRadiusControl, 0, 500); | |
| } | |
| #ifdef SK_DEBUG | |
| else if (index == (int) SK_ARRAY_COUNT(fPts) + 3) { | |
| gDebugStrokerError = SkTMax(FLT_EPSILON, MapScreenYtoValue(click->fICurr.fY, | |
| fErrorControl, kStrokerErrorMin, kStrokerErrorMax)); | |
| gDebugStrokerErrorSet = true; | |
| } | |
| #endif | |
| else if (index == (int) SK_ARRAY_COUNT(fPts) + 4) { | |
| fWidth = SkTMax(FLT_EPSILON, MapScreenYtoValue(click->fICurr.fY, fWidthControl, | |
| kWidthMin, kWidthMax)); | |
| fAnimate = fWidth <= kWidthMin; | |
| } | |
| return true; | |
| } | |
| private: | |
| typedef SkView INHERITED; | |
| }; | |
| /////////////////////////////////////////////////////////////////////////////// | |
| static SkView* F2() { return new QuadStrokerView; } | |
| static SkViewRegister gR2(F2); |