1 /*
2  * Copyright 2012 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #include "include/core/SkBlendMode.h"
9 #include "include/core/SkCanvas.h"
10 #include "include/core/SkColor.h"
11 #include "include/core/SkFont.h"
12 #include "include/core/SkImageInfo.h"
13 #include "include/core/SkMatrix.h"
14 #include "include/core/SkPaint.h"
15 #include "include/core/SkPath.h"
16 #include "include/core/SkPathMeasure.h"
17 #include "include/core/SkPoint.h"
18 #include "include/core/SkRRect.h"
19 #include "include/core/SkRect.h"
20 #include "include/core/SkRefCnt.h"
21 #include "include/core/SkScalar.h"
22 #include "include/core/SkShader.h"
23 #include "include/core/SkString.h"
24 #include "include/core/SkSurface.h"
25 #include "include/core/SkTypes.h"
26 #include "include/private/SkTArray.h"
27 #include "include/private/SkTemplates.h"
28 #include "include/utils/SkTextUtils.h"
29 #include "samplecode/Sample.h"
30 #include "src/core/SkGeometry.h"
31 #include "src/core/SkPointPriv.h"
32 #include "src/core/SkStroke.h"
33 #include "tools/ToolUtils.h"
34 
35 #include <cfloat>
36 
37 class SkEvent;
38 
hittest(const SkPoint & target,SkScalar x,SkScalar y)39 static bool hittest(const SkPoint& target, SkScalar x, SkScalar y) {
40     const SkScalar TOL = 7;
41     return SkPoint::Distance(target, SkPoint::Make(x, y)) <= TOL;
42 }
43 
getOnCurvePoints(const SkPath & path,SkPoint storage[])44 static int getOnCurvePoints(const SkPath& path, SkPoint storage[]) {
45     SkPath::RawIter iter(path);
46     SkPoint pts[4];
47     SkPath::Verb verb;
48 
49     int count = 0;
50     while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
51         switch (verb) {
52             case SkPath::kMove_Verb:
53             case SkPath::kLine_Verb:
54             case SkPath::kQuad_Verb:
55             case SkPath::kConic_Verb:
56             case SkPath::kCubic_Verb:
57                 storage[count++] = pts[0];
58                 break;
59             default:
60                 break;
61         }
62     }
63     return count;
64 }
65 
getContourCounts(const SkPath & path,SkTArray<int> * contourCounts)66 static void getContourCounts(const SkPath& path, SkTArray<int>* contourCounts) {
67     SkPath::RawIter iter(path);
68     SkPoint pts[4];
69     SkPath::Verb verb;
70 
71     int count = 0;
72     while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
73         switch (verb) {
74             case SkPath::kMove_Verb:
75             case SkPath::kLine_Verb:
76                 count += 1;
77                 break;
78             case SkPath::kQuad_Verb:
79             case SkPath::kConic_Verb:
80                 count += 2;
81                 break;
82             case SkPath::kCubic_Verb:
83                 count += 3;
84                 break;
85             case SkPath::kClose_Verb:
86                 contourCounts->push_back(count);
87                 count = 0;
88                 break;
89             default:
90                 break;
91         }
92     }
93     if (count > 0) {
94         contourCounts->push_back(count);
95     }
96 }
97 
erase(const sk_sp<SkSurface> & surface)98 static void erase(const sk_sp<SkSurface>& surface) {
99     SkCanvas* canvas = surface->getCanvas();
100     if (canvas) {
101         canvas->clear(SK_ColorTRANSPARENT);
102     }
103 }
104 
105 struct StrokeTypeButton {
106     SkRect fBounds;
107     char fLabel;
108     bool fEnabled;
109 };
110 
111 struct CircleTypeButton : public StrokeTypeButton {
112     bool fFill;
113 };
114 
115 class QuadStrokerView : public Sample {
116     enum {
117         SKELETON_COLOR = 0xFF0000FF,
118         WIREFRAME_COLOR = 0x80FF0000
119     };
120 
121     enum {
122         kCount = 18
123     };
124     SkPoint fPts[kCount];
125     SkRect fWeightControl;
126     SkRect fRadiusControl;
127     SkRect fErrorControl;
128     SkRect fWidthControl;
129     SkRect fBounds;
130     SkMatrix fMatrix, fInverse;
131     sk_sp<SkShader> fShader;
132     sk_sp<SkSurface> fMinSurface;
133     sk_sp<SkSurface> fMaxSurface;
134     StrokeTypeButton fCubicButton;
135     StrokeTypeButton fConicButton;
136     StrokeTypeButton fQuadButton;
137     StrokeTypeButton fArcButton;
138     StrokeTypeButton fRRectButton;
139     CircleTypeButton fCircleButton;
140     StrokeTypeButton fTextButton;
141     SkString fText;
142     SkScalar fTextSize;
143     SkScalar fWeight;
144     SkScalar fRadius;
145     SkScalar fWidth, fDWidth;
146     SkScalar fWidthScale;
147     int fW, fH, fZoom;
148     bool fAnimate;
149     bool fDrawRibs;
150     bool fDrawTangents;
151     bool fDrawTDivs;
152 #ifdef SK_DEBUG
153     #define kStrokerErrorMin 0.001f
154     #define kStrokerErrorMax 5
155 #endif
156     #define kWidthMin 1
157     #define kWidthMax 100
158 public:
QuadStrokerView()159     QuadStrokerView() {
160         this->setBGColor(SK_ColorLTGRAY);
161 
162         fPts[0].set(50, 200);  // cubic
163         fPts[1].set(50, 100);
164         fPts[2].set(150, 50);
165         fPts[3].set(300, 50);
166 
167         fPts[4].set(350, 200);  // conic
168         fPts[5].set(350, 100);
169         fPts[6].set(450, 50);
170 
171         fPts[7].set(150, 300);  // quad
172         fPts[8].set(150, 200);
173         fPts[9].set(250, 150);
174 
175         fPts[10].set(250, 200);  // arc
176         fPts[11].set(250, 300);
177         fPts[12].set(150, 350);
178 
179         fPts[13].set(200, 200); // rrect
180         fPts[14].set(400, 400);
181 
182         fPts[15].set(250, 250);  // oval
183         fPts[16].set(450, 450);
184 
185         fText = "a";
186         fTextSize = 12;
187         fWidth = 50;
188         fDWidth = 0.25f;
189         fWeight = 1;
190         fRadius = 150;
191 
192         fCubicButton.fLabel = 'C';
193         fCubicButton.fEnabled = false;
194         fConicButton.fLabel = 'K';
195         fConicButton.fEnabled = false;
196         fQuadButton.fLabel = 'Q';
197         fQuadButton.fEnabled = false;
198         fArcButton.fLabel = 'A';
199         fArcButton.fEnabled = true;
200         fRRectButton.fLabel = 'R';
201         fRRectButton.fEnabled = false;
202         fCircleButton.fLabel = 'O';
203         fCircleButton.fEnabled = true;
204         fCircleButton.fFill = true;
205         fTextButton.fLabel = 'T';
206         fTextButton.fEnabled = false;
207         fAnimate = false;
208         setAsNeeded();
209     }
210 
211 protected:
name()212     SkString name() override { return SkString("QuadStroker"); }
213 
onChar(SkUnichar uni)214     bool onChar(SkUnichar uni) override {
215         if (fTextButton.fEnabled) {
216             switch (uni) {
217                 case ' ':
218                     fText = "";
219                     break;
220                 case '-':
221                     fTextSize = std::max(1.0f, fTextSize - 1);
222                     break;
223                 case '+':
224                 case '=':
225                     fTextSize += 1;
226                     break;
227                 default:
228                     fText.appendUnichar(uni);
229             }
230             return true;
231         }
232         return false;
233     }
234 
onSizeChange()235     void onSizeChange() override {
236         fRadiusControl.setXYWH(this->width() - 200, 30, 30, 400);
237         fWeightControl.setXYWH(this->width() - 150, 30, 30, 400);
238         fErrorControl.setXYWH(this->width() - 100, 30, 30, 400);
239         fWidthControl.setXYWH(this->width() -  50, 30, 30, 400);
240         int buttonOffset = 450;
241         fCubicButton.fBounds.setXYWH(this->width() - 50, SkIntToScalar(buttonOffset), 30, 30);
242         buttonOffset += 50;
243         fConicButton.fBounds.setXYWH(this->width() - 50, SkIntToScalar(buttonOffset), 30, 30);
244         buttonOffset += 50;
245         fQuadButton.fBounds.setXYWH(this->width() - 50, SkIntToScalar(buttonOffset), 30, 30);
246         buttonOffset += 50;
247         fArcButton.fBounds.setXYWH(this->width() - 50, SkIntToScalar(buttonOffset), 30, 30);
248         buttonOffset += 50;
249         fRRectButton.fBounds.setXYWH(this->width() - 50, SkIntToScalar(buttonOffset), 30, 30);
250         buttonOffset += 50;
251         fCircleButton.fBounds.setXYWH(this->width() - 50, SkIntToScalar(buttonOffset), 30, 30);
252         buttonOffset += 50;
253         fTextButton.fBounds.setXYWH(this->width() - 50, SkIntToScalar(buttonOffset), 30, 30);
254         this->INHERITED::onSizeChange();
255     }
256 
copyMinToMax()257      void copyMinToMax() {
258         erase(fMaxSurface);
259         SkCanvas* canvas = fMaxSurface->getCanvas();
260         canvas->save();
261         canvas->concat(fMatrix);
262         fMinSurface->draw(canvas, 0, 0, nullptr);
263         canvas->restore();
264 
265         SkPaint paint;
266         paint.setBlendMode(SkBlendMode::kClear);
267         for (int iy = 1; iy < fH; ++iy) {
268             SkScalar y = SkIntToScalar(iy * fZoom);
269             canvas->drawLine(0, y - SK_ScalarHalf, 999, y - SK_ScalarHalf, paint);
270         }
271         for (int ix = 1; ix < fW; ++ix) {
272             SkScalar x = SkIntToScalar(ix * fZoom);
273             canvas->drawLine(x - SK_ScalarHalf, 0, x - SK_ScalarHalf, 999, paint);
274         }
275     }
276 
setWHZ(int width,int height,int zoom)277    void setWHZ(int width, int height, int zoom) {
278         fZoom = zoom;
279         fBounds.setIWH(width * zoom, height * zoom);
280         fMatrix.setScale(SkIntToScalar(zoom), SkIntToScalar(zoom));
281         fInverse.setScale(SK_Scalar1 / zoom, SK_Scalar1 / zoom);
282         fShader = ToolUtils::create_checkerboard_shader(0xFFCCCCCC, 0xFFFFFFFF, zoom);
283 
284         SkImageInfo info = SkImageInfo::MakeN32Premul(width, height);
285         fMinSurface = SkSurface::MakeRaster(info);
286         info = info.makeWH(width * zoom, height * zoom);
287         fMaxSurface = SkSurface::MakeRaster(info);
288     }
289 
draw_points(SkCanvas * canvas,const SkPath & path,SkColor color,bool show_lines)290     void draw_points(SkCanvas* canvas, const SkPath& path, SkColor color,
291                      bool show_lines) {
292         SkPaint paint;
293         paint.setColor(color);
294         paint.setAlpha(0x80);
295         paint.setAntiAlias(true);
296         int n = path.countPoints();
297         SkAutoSTArray<32, SkPoint> pts(n);
298         if (show_lines && fDrawTangents) {
299             SkTArray<int> contourCounts;
300             getContourCounts(path, &contourCounts);
301             SkPoint* ptPtr = pts.get();
302             for (int i = 0; i < contourCounts.count(); ++i) {
303                 int count = contourCounts[i];
304                 path.getPoints(ptPtr, count);
305                 canvas->drawPoints(SkCanvas::kPolygon_PointMode, count, ptPtr, paint);
306                 ptPtr += count;
307             }
308         } else {
309             n = getOnCurvePoints(path, pts.get());
310         }
311         paint.setStrokeWidth(5);
312         canvas->drawPoints(SkCanvas::kPoints_PointMode, n, pts.get(), paint);
313     }
314 
draw_ribs(SkCanvas * canvas,const SkPath & path,SkScalar width,SkColor color)315     void draw_ribs(SkCanvas* canvas, const SkPath& path, SkScalar width,
316                    SkColor color) {
317         const SkScalar radius = width / 2;
318 
319         SkPathMeasure meas(path, false);
320         SkScalar total = meas.getLength();
321 
322         SkScalar delta = 8;
323         SkPaint paint, labelP;
324         paint.setColor(color);
325         labelP.setColor(color & 0xff5f9f5f);
326         SkFont font;
327         SkPoint pos, tan;
328         int index = 0;
329         for (SkScalar dist = 0; dist <= total; dist += delta) {
330             if (meas.getPosTan(dist, &pos, &tan)) {
331                 tan.scale(radius);
332                 SkPointPriv::RotateCCW(&tan);
333                 canvas->drawLine(pos.x() + tan.x(), pos.y() + tan.y(),
334                                  pos.x() - tan.x(), pos.y() - tan.y(), paint);
335                 if (0 == index % 10) {
336                     SkString label;
337                     label.appendS32(index);
338                     SkRect dot = SkRect::MakeXYWH(pos.x() - 2, pos.y() - 2, 4, 4);
339                     canvas->drawRect(dot, labelP);
340                     canvas->drawString(label,
341                         pos.x() - tan.x() * 1.25f, pos.y() - tan.y() * 1.25f, font, labelP);
342                 }
343             }
344             ++index;
345         }
346     }
347 
draw_t_divs(SkCanvas * canvas,const SkPath & path,SkScalar width,SkColor color)348     void draw_t_divs(SkCanvas* canvas, const SkPath& path, SkScalar width, SkColor color) {
349         const SkScalar radius = width / 2;
350         SkPaint paint;
351         paint.setColor(color);
352         SkPathMeasure meas(path, false);
353         SkScalar total = meas.getLength();
354         SkScalar delta = 8;
355         int ribs = 0;
356         for (SkScalar dist = 0; dist <= total; dist += delta) {
357             ++ribs;
358         }
359         SkPath::RawIter iter(path);
360         SkPoint pts[4];
361         if (SkPath::kMove_Verb != iter.next(pts)) {
362             SkASSERT(0);
363             return;
364         }
365         SkPath::Verb verb = iter.next(pts);
366         SkASSERT(SkPath::kLine_Verb <= verb && verb <= SkPath::kCubic_Verb);
367         SkPoint pos, tan;
368         for (int index = 0; index < ribs; ++index) {
369             SkScalar t = (SkScalar) index / ribs;
370             switch (verb) {
371                 case SkPath::kLine_Verb:
372                     tan = pts[1] - pts[0];
373                     pos = pts[0];
374                     pos.fX += tan.fX * t;
375                     pos.fY += tan.fY * t;
376                     break;
377                 case SkPath::kQuad_Verb:
378                     pos = SkEvalQuadAt(pts, t);
379                     tan = SkEvalQuadTangentAt(pts, t);
380                     break;
381                 case SkPath::kConic_Verb: {
382                     SkConic conic(pts, iter.conicWeight());
383                     pos = conic.evalAt(t);
384                     tan = conic.evalTangentAt(t);
385                     } break;
386                 case SkPath::kCubic_Verb:
387                     SkEvalCubicAt(pts, t, &pos, &tan, nullptr);
388                     break;
389                 default:
390                     SkASSERT(0);
391                     return;
392             }
393             tan.setLength(radius);
394             SkPointPriv::RotateCCW(&tan);
395             canvas->drawLine(pos.x() + tan.x(), pos.y() + tan.y(),
396                                 pos.x() - tan.x(), pos.y() - tan.y(), paint);
397             if (0 == index % 10) {
398                 SkString label;
399                 label.appendS32(index);
400                 canvas->drawString(label,
401                     pos.x() + tan.x() * 1.25f, pos.y() + tan.y() * 1.25f, SkFont(), paint);
402             }
403         }
404     }
405 
draw_stroke(SkCanvas * canvas,const SkPath & path,SkScalar width,SkScalar scale,bool drawText)406     void draw_stroke(SkCanvas* canvas, const SkPath& path, SkScalar width, SkScalar scale,
407             bool drawText) {
408         if (path.isEmpty()) {
409             return;
410         }
411         SkRect bounds = path.getBounds();
412         this->setWHZ(SkScalarCeilToInt(bounds.right()), drawText
413                 ? SkScalarRoundToInt(scale * 3 / 2) : SkScalarRoundToInt(scale),
414                 SkScalarRoundToInt(950.0f / scale));
415         erase(fMinSurface);
416         SkPaint paint;
417         paint.setColor(0x1f1f0f0f);
418         paint.setStyle(SkPaint::kStroke_Style);
419         paint.setStrokeWidth(width * scale * scale);
420         paint.setColor(0x3f0f1f3f);
421         if (drawText) {
422             fMinSurface->getCanvas()->drawPath(path, paint);
423             this->copyMinToMax();
424             fMaxSurface->draw(canvas, 0, 0, nullptr);
425         }
426         paint.setAntiAlias(true);
427         paint.setStyle(SkPaint::kStroke_Style);
428         paint.setStrokeWidth(1);
429 
430         paint.setColor(SKELETON_COLOR);
431         SkPath scaled;
432         SkMatrix matrix;
433         matrix.reset();
434         matrix.setScale(950 / scale, 950 / scale);
435         if (drawText) {
436             path.transform(matrix, &scaled);
437         } else {
438             scaled = path;
439         }
440         canvas->drawPath(scaled, paint);
441         draw_points(canvas, scaled, SKELETON_COLOR, true);
442 
443         if (fDrawRibs) {
444             draw_ribs(canvas, scaled, width, 0xFF00FF00);
445         }
446 
447         if (fDrawTDivs) {
448             draw_t_divs(canvas, scaled, width, 0xFF3F3F00);
449         }
450 
451         SkPath fill;
452 
453         SkPaint p;
454         p.setStyle(SkPaint::kStroke_Style);
455         if (drawText) {
456             p.setStrokeWidth(width * scale * scale);
457         } else {
458             p.setStrokeWidth(width);
459         }
460         p.getFillPath(path, &fill);
461         SkPath scaledFill;
462         if (drawText) {
463             fill.transform(matrix, &scaledFill);
464         } else {
465             scaledFill = fill;
466         }
467         paint.setColor(WIREFRAME_COLOR);
468         canvas->drawPath(scaledFill, paint);
469         draw_points(canvas, scaledFill, WIREFRAME_COLOR, false);
470     }
471 
draw_fill(SkCanvas * canvas,const SkRect & rect,SkScalar width)472     void draw_fill(SkCanvas* canvas, const SkRect& rect, SkScalar width) {
473         if (rect.isEmpty()) {
474             return;
475         }
476         SkPaint paint;
477         paint.setColor(0x1f1f0f0f);
478         paint.setStyle(SkPaint::kStroke_Style);
479         paint.setStrokeWidth(width);
480         SkPath path;
481         SkScalar maxSide = std::max(rect.width(), rect.height()) / 2;
482         SkPoint center = { rect.fLeft + maxSide, rect.fTop + maxSide };
483         path.addCircle(center.fX, center.fY, maxSide);
484         canvas->drawPath(path, paint);
485         paint.setStyle(SkPaint::kFill_Style);
486         path.reset();
487         path.addCircle(center.fX, center.fY, maxSide - width / 2);
488         paint.setColor(0x3f0f1f3f);
489         canvas->drawPath(path, paint);
490         path.reset();
491         path.setFillType(SkPathFillType::kEvenOdd);
492         path.addCircle(center.fX, center.fY, maxSide + width / 2);
493         SkRect outside = SkRect::MakeXYWH(center.fX - maxSide - width, center.fY - maxSide - width,
494                 (maxSide + width) * 2, (maxSide + width) * 2);
495         path.addRect(outside);
496         canvas->drawPath(path, paint);
497     }
498 
draw_button(SkCanvas * canvas,const StrokeTypeButton & button)499     void draw_button(SkCanvas* canvas, const StrokeTypeButton& button) {
500         SkPaint paint;
501         paint.setAntiAlias(true);
502         paint.setStyle(SkPaint::kStroke_Style);
503         paint.setColor(button.fEnabled ? 0xFF3F0000 : 0x6F3F0000);
504         canvas->drawRect(button.fBounds, paint);
505         paint.setColor(button.fEnabled ? 0xFF3F0000 : 0x6F3F0000);
506         paint.setStyle(SkPaint::kFill_Style);
507         SkFont font;
508         font.setSize(25.0f);
509         SkTextUtils::Draw(canvas, &button.fLabel, 1, SkTextEncoding::kUTF8,
510                 button.fBounds.centerX(), button.fBounds.fBottom - 5,
511                 font, paint, SkTextUtils::kCenter_Align);
512     }
513 
draw_control(SkCanvas * canvas,const SkRect & bounds,SkScalar value,SkScalar min,SkScalar max,const char * name)514     void draw_control(SkCanvas* canvas, const SkRect& bounds, SkScalar value,
515             SkScalar min, SkScalar max, const char* name) {
516         SkPaint paint;
517         paint.setAntiAlias(true);
518         paint.setStyle(SkPaint::kStroke_Style);
519         canvas->drawRect(bounds, paint);
520         SkScalar scale = max - min;
521         SkScalar yPos = bounds.fTop + (value - min) * bounds.height() / scale;
522         paint.setColor(0xFFFF0000);
523         canvas->drawLine(bounds.fLeft - 5, yPos, bounds.fRight + 5, yPos, paint);
524         SkString label;
525         label.printf("%0.3g", value);
526         paint.setColor(0xFF000000);
527         paint.setStyle(SkPaint::kFill_Style);
528         SkFont font(nullptr, 11.0f);
529         canvas->drawString(label, bounds.fLeft + 5, yPos - 5, font, paint);
530         font.setSize(13.0f);
531         canvas->drawString(name, bounds.fLeft, bounds.bottom() + 11, font, paint);
532     }
533 
setForGeometry()534     void setForGeometry() {
535         fDrawRibs = true;
536         fDrawTangents = true;
537         fDrawTDivs = false;
538         fWidthScale = 1;
539     }
540 
setForText()541     void setForText() {
542         fDrawRibs = fDrawTangents = fDrawTDivs = false;
543         fWidthScale = 0.002f;
544     }
545 
setForSingles()546     void setForSingles() {
547         setForGeometry();
548         fDrawTDivs = true;
549     }
550 
setAsNeeded()551     void setAsNeeded() {
552         if (fConicButton.fEnabled || fCubicButton.fEnabled || fQuadButton.fEnabled) {
553             setForSingles();
554         } else if (fRRectButton.fEnabled || fCircleButton.fEnabled || fArcButton.fEnabled) {
555             setForGeometry();
556         } else {
557             setForText();
558         }
559     }
560 
arcCenter(SkPoint * center)561     bool arcCenter(SkPoint* center) {
562         SkPath path;
563         path.moveTo(fPts[10]);
564         path.arcTo(fPts[11], fPts[12], fRadius);
565         SkPath::Iter iter(path, false);
566         SkPoint pts[4];
567         iter.next(pts);
568         if (SkPath::kLine_Verb == iter.next(pts)) {
569             iter.next(pts);
570         }
571         SkVector before = pts[0] - pts[1];
572         SkVector after = pts[1] - pts[2];
573         before.setLength(fRadius);
574         after.setLength(fRadius);
575         SkVector beforeCCW, afterCCW;
576         SkPointPriv::RotateCCW(before, &beforeCCW);
577         SkPointPriv::RotateCCW(after, &afterCCW);
578         beforeCCW += pts[0];
579         afterCCW += pts[2];
580         *center = beforeCCW;
581         if (SkScalarNearlyEqual(beforeCCW.fX, afterCCW.fX)
582                 && SkScalarNearlyEqual(beforeCCW.fY, afterCCW.fY)) {
583             return true;
584         }
585         SkVector beforeCW, afterCW;
586         SkPointPriv::RotateCW(before, &beforeCW);
587         SkPointPriv::RotateCW(after, &afterCW);
588         beforeCW += pts[0];
589         afterCW += pts[2];
590         *center = beforeCW;
591         return SkScalarNearlyEqual(beforeCW.fX, afterCW.fX)
592                 && SkScalarNearlyEqual(beforeCCW.fY, afterCW.fY);
593     }
594 
onDrawContent(SkCanvas * canvas)595     void onDrawContent(SkCanvas* canvas) override {
596         SkPath path;
597         SkScalar width = fWidth;
598 
599         if (fCubicButton.fEnabled) {
600             path.moveTo(fPts[0]);
601             path.cubicTo(fPts[1], fPts[2], fPts[3]);
602             setForSingles();
603             draw_stroke(canvas, path, width, 950, false);
604         }
605 
606         if (fConicButton.fEnabled) {
607             path.reset();
608             path.moveTo(fPts[4]);
609             path.conicTo(fPts[5], fPts[6], fWeight);
610             setForSingles();
611             draw_stroke(canvas, path, width, 950, false);
612         }
613 
614         if (fQuadButton.fEnabled) {
615             path.reset();
616             path.moveTo(fPts[7]);
617             path.quadTo(fPts[8], fPts[9]);
618             setForSingles();
619             draw_stroke(canvas, path, width, 950, false);
620         }
621 
622         if (fArcButton.fEnabled) {
623             path.reset();
624             path.moveTo(fPts[10]);
625             path.arcTo(fPts[11], fPts[12], fRadius);
626             setForGeometry();
627             draw_stroke(canvas, path, width, 950, false);
628             SkPath pathPts;
629             pathPts.moveTo(fPts[10]);
630             pathPts.lineTo(fPts[11]);
631             pathPts.lineTo(fPts[12]);
632             draw_points(canvas, pathPts, SK_ColorDKGRAY, true);
633         }
634 
635         if (fRRectButton.fEnabled) {
636             SkScalar rad = 32;
637             SkRect r;
638             r.setBounds(&fPts[13], 2);
639             path.reset();
640             SkRRect rr;
641             rr.setRectXY(r, rad, rad);
642             path.addRRect(rr);
643             setForGeometry();
644             draw_stroke(canvas, path, width, 950, false);
645 
646             path.reset();
647             SkRRect rr2;
648             rr.inset(width/2, width/2, &rr2);
649             path.addRRect(rr2, SkPathDirection::kCCW);
650             rr.inset(-width/2, -width/2, &rr2);
651             path.addRRect(rr2, SkPathDirection::kCW);
652             SkPaint paint;
653             paint.setAntiAlias(true);
654             paint.setColor(0x40FF8844);
655             canvas->drawPath(path, paint);
656         }
657 
658         if (fCircleButton.fEnabled) {
659             path.reset();
660             SkRect r;
661             r.setBounds(&fPts[15], 2);
662             path.addOval(r);
663             setForGeometry();
664             if (fCircleButton.fFill) {
665                 if (fArcButton.fEnabled) {
666                     SkPoint center;
667                     if (arcCenter(&center)) {
668                         r.setLTRB(center.fX - fRadius, center.fY - fRadius,
669                                   center.fX + fRadius, center.fY + fRadius);
670                     }
671                 }
672                 draw_fill(canvas, r, width);
673             } else {
674                 draw_stroke(canvas, path, width, 950, false);
675             }
676         }
677 
678         if (fTextButton.fEnabled) {
679             path.reset();
680             SkFont font;
681             font.setSize(fTextSize);
682             SkTextUtils::GetPath(fText.c_str(), fText.size(), SkTextEncoding::kUTF8,
683                                  0, fTextSize, font, &path);
684             setForText();
685             draw_stroke(canvas, path, width * fWidthScale / fTextSize, fTextSize, true);
686         }
687 
688         if (fAnimate) {
689             fWidth += fDWidth;
690             if (fDWidth > 0 && fWidth > kWidthMax) {
691                 fDWidth = -fDWidth;
692             } else if (fDWidth < 0 && fWidth < kWidthMin) {
693                 fDWidth = -fDWidth;
694             }
695         }
696         setAsNeeded();
697         if (fConicButton.fEnabled) {
698             draw_control(canvas, fWeightControl, fWeight, 0, 5, "weight");
699         }
700         if (fArcButton.fEnabled) {
701             draw_control(canvas, fRadiusControl, fRadius, 0, 500, "radius");
702         }
703 #ifdef SK_DEBUG
704         draw_control(canvas, fErrorControl, gDebugStrokerError, kStrokerErrorMin, kStrokerErrorMax,
705                 "error");
706 #endif
707         draw_control(canvas, fWidthControl, fWidth * fWidthScale, kWidthMin * fWidthScale,
708                 kWidthMax * fWidthScale, "width");
709         draw_button(canvas, fQuadButton);
710         draw_button(canvas, fCubicButton);
711         draw_button(canvas, fConicButton);
712         draw_button(canvas, fArcButton);
713         draw_button(canvas, fRRectButton);
714         draw_button(canvas, fCircleButton);
715         draw_button(canvas, fTextButton);
716     }
717 
718     class MyClick : public Click {
719     public:
720         int fIndex;
MyClick(int index)721         MyClick(int index) : fIndex(index) {}
722     };
723 
onFindClickHandler(SkScalar x,SkScalar y,skui::ModifierKey modi)724     virtual Sample::Click* onFindClickHandler(SkScalar x, SkScalar y,
725                                               skui::ModifierKey modi) override {
726         for (size_t i = 0; i < SK_ARRAY_COUNT(fPts); ++i) {
727             if (hittest(fPts[i], x, y)) {
728                 return new MyClick((int)i);
729             }
730         }
731         const SkRect& rectPt = SkRect::MakeXYWH(x, y, 1, 1);
732         if (fWeightControl.contains(rectPt)) {
733             return new MyClick((int) SK_ARRAY_COUNT(fPts) + 1);
734         }
735         if (fRadiusControl.contains(rectPt)) {
736             return new MyClick((int) SK_ARRAY_COUNT(fPts) + 2);
737         }
738 #ifdef SK_DEBUG
739         if (fErrorControl.contains(rectPt)) {
740             return new MyClick((int) SK_ARRAY_COUNT(fPts) + 3);
741         }
742 #endif
743         if (fWidthControl.contains(rectPt)) {
744             return new MyClick((int) SK_ARRAY_COUNT(fPts) + 4);
745         }
746         if (fCubicButton.fBounds.contains(rectPt)) {
747             fCubicButton.fEnabled ^= true;
748             return new MyClick((int) SK_ARRAY_COUNT(fPts) + 5);
749         }
750         if (fConicButton.fBounds.contains(rectPt)) {
751             fConicButton.fEnabled ^= true;
752             return new MyClick((int) SK_ARRAY_COUNT(fPts) + 6);
753         }
754         if (fQuadButton.fBounds.contains(rectPt)) {
755             fQuadButton.fEnabled ^= true;
756             return new MyClick((int) SK_ARRAY_COUNT(fPts) + 7);
757         }
758         if (fArcButton.fBounds.contains(rectPt)) {
759             fArcButton.fEnabled ^= true;
760             return new MyClick((int) SK_ARRAY_COUNT(fPts) + 8);
761         }
762         if (fRRectButton.fBounds.contains(rectPt)) {
763             fRRectButton.fEnabled ^= true;
764             return new MyClick((int) SK_ARRAY_COUNT(fPts) + 9);
765         }
766         if (fCircleButton.fBounds.contains(rectPt)) {
767             bool wasEnabled = fCircleButton.fEnabled;
768             fCircleButton.fEnabled = !fCircleButton.fFill;
769             fCircleButton.fFill = wasEnabled && !fCircleButton.fFill;
770             return new MyClick((int) SK_ARRAY_COUNT(fPts) + 10);
771         }
772         if (fTextButton.fBounds.contains(rectPt)) {
773             fTextButton.fEnabled ^= true;
774             return new MyClick((int) SK_ARRAY_COUNT(fPts) + 11);
775         }
776         return nullptr;
777     }
778 
MapScreenYtoValue(SkScalar y,const SkRect & control,SkScalar min,SkScalar max)779     static SkScalar MapScreenYtoValue(SkScalar y, const SkRect& control, SkScalar min,
780             SkScalar max) {
781         return (y - control.fTop) / control.height() * (max - min) + min;
782     }
783 
onClick(Click * click)784     bool onClick(Click* click) override {
785         int index = ((MyClick*)click)->fIndex;
786         if (index < (int) SK_ARRAY_COUNT(fPts)) {
787             fPts[index].offset(click->fCurr.fX - click->fPrev.fX,
788                                click->fCurr.fY - click->fPrev.fY);
789         } else if (index == (int) SK_ARRAY_COUNT(fPts) + 1) {
790             fWeight = MapScreenYtoValue(click->fCurr.fY, fWeightControl, 0, 5);
791         } else if (index == (int) SK_ARRAY_COUNT(fPts) + 2) {
792             fRadius = MapScreenYtoValue(click->fCurr.fY, fRadiusControl, 0, 500);
793         }
794 #ifdef SK_DEBUG
795         else if (index == (int) SK_ARRAY_COUNT(fPts) + 3) {
796             gDebugStrokerError = std::max(FLT_EPSILON, MapScreenYtoValue(click->fCurr.fY,
797                     fErrorControl, kStrokerErrorMin, kStrokerErrorMax));
798             gDebugStrokerErrorSet = true;
799         }
800 #endif
801         else if (index == (int) SK_ARRAY_COUNT(fPts) + 4) {
802             fWidth = std::max(FLT_EPSILON, MapScreenYtoValue(click->fCurr.fY, fWidthControl,
803                     kWidthMin, kWidthMax));
804             fAnimate = fWidth <= kWidthMin;
805         }
806         return true;
807     }
808 
809 private:
810     typedef Sample INHERITED;
811 };
812 
813 ///////////////////////////////////////////////////////////////////////////////
814 
815 DEF_SAMPLE( return new QuadStrokerView(); )
816