blob: 8eb82b9512cb73566427f10e8064ff75c343ea2a [file] [log] [blame]
caryclark64022c12016-05-27 05:13:26 -07001/*
2 * Copyright 2015 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
Mike Kleinc0bd9f92019-04-23 12:05:21 -05008#include "include/core/SkBitmap.h"
9#include "include/core/SkCanvas.h"
Hal Canary8918d532019-07-12 10:04:14 -040010#include "include/core/SkString.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050011#include "include/private/SkMacros.h"
Hal Canary8918d532019-07-12 10:04:14 -040012#include "include/utils/SkTextUtils.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050013#include "samplecode/Sample.h"
14#include "src/core/SkGeometry.h"
Chris Dalton8d3eb242020-05-04 10:43:33 -060015#include "src/core/SkPathPriv.h"
Hal Canary8918d532019-07-12 10:04:14 -040016#include "src/core/SkPointPriv.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050017#include "src/pathops/SkIntersections.h"
18#include "src/pathops/SkOpEdgeBuilder.h"
Chris Daltone7ee0692020-01-06 10:18:30 -070019#include "tools/ToolUtils.h"
caryclark64022c12016-05-27 05:13:26 -070020
21#if 0
22void SkStrokeSegment::dump() const {
23 SkDebugf("{{{%1.9g,%1.9g}, {%1.9g,%1.9g}", fPts[0].fX, fPts[0].fY, fPts[1].fX, fPts[1].fY);
24 if (SkPath::kQuad_Verb == fVerb) {
25 SkDebugf(", {%1.9g,%1.9g}", fPts[2].fX, fPts[2].fY);
26 }
27 SkDebugf("}}");
28#ifdef SK_DEBUG
29 SkDebugf(" id=%d", fDebugID);
30#endif
31 SkDebugf("\n");
32}
33
34void SkStrokeSegment::dumpAll() const {
35 const SkStrokeSegment* segment = this;
36 while (segment) {
37 segment->dump();
38 segment = segment->fNext;
39 }
40}
41
42void SkStrokeTriple::dump() const {
43 SkDebugf("{{{%1.9g,%1.9g}, {%1.9g,%1.9g}", fPts[0].fX, fPts[0].fY, fPts[1].fX, fPts[1].fY);
44 if (SkPath::kQuad_Verb <= fVerb) {
45 SkDebugf(", {%1.9g,%1.9g}", fPts[2].fX, fPts[2].fY);
46 }
47 if (SkPath::kCubic_Verb == fVerb) {
48 SkDebugf(", {%1.9g,%1.9g}", fPts[3].fX, fPts[3].fY);
49 } else if (SkPath::kConic_Verb == fVerb) {
50 SkDebugf(", %1.9g", weight());
51 }
52 SkDebugf("}}");
53#ifdef SK_DEBUG
54 SkDebugf(" triple id=%d", fDebugID);
55#endif
56 SkDebugf("\ninner:\n");
57 fInner->dumpAll();
58 SkDebugf("outer:\n");
59 fOuter->dumpAll();
60 SkDebugf("join:\n");
61 fJoin->dumpAll();
62}
63
64void SkStrokeTriple::dumpAll() const {
65 const SkStrokeTriple* triple = this;
66 while (triple) {
67 triple->dump();
68 triple = triple->fNext;
69 }
70}
71
72void SkStrokeContour::dump() const {
73#ifdef SK_DEBUG
74 SkDebugf("id=%d ", fDebugID);
75#endif
76 SkDebugf("head:\n");
77 fHead->dumpAll();
78 SkDebugf("head cap:\n");
79 fHeadCap->dumpAll();
80 SkDebugf("tail cap:\n");
81 fTailCap->dumpAll();
82}
83
84void SkStrokeContour::dumpAll() const {
85 const SkStrokeContour* contour = this;
86 while (contour) {
87 contour->dump();
88 contour = contour->fNext;
89 }
90}
91#endif
92
93SkScalar gCurveDistance = 10;
94
95#if 0 // unused
96static SkPath::Verb get_path_verb(int index, const SkPath& path) {
97 if (index < 0) {
98 return SkPath::kMove_Verb;
99 }
100 SkPoint pts[4];
101 SkPath::Verb verb;
102 SkPath::Iter iter(path, true);
103 int counter = -1;
104 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
105 if (++counter < index) {
106 continue;
107 }
108 return verb;
109 }
110 SkASSERT(0);
111 return SkPath::kMove_Verb;
112}
113#endif
114
115static SkScalar get_path_weight(int index, const SkPath& path) {
116 SkPoint pts[4];
117 SkPath::Verb verb;
118 SkPath::Iter iter(path, true);
119 int counter = -1;
120 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
121 if (++counter < index) {
122 continue;
123 }
124 return verb == SkPath::kConic_Verb ? iter.conicWeight() : 1;
125 }
126 SkASSERT(0);
127 return 0;
128}
129
caryclark64022c12016-05-27 05:13:26 -0700130static void add_path_segment(int index, SkPath* path) {
131 SkPath result;
caryclark64022c12016-05-27 05:13:26 -0700132 SkPoint firstPt = { 0, 0 }; // init to avoid warning
133 SkPoint lastPt = { 0, 0 }; // init to avoid warning
caryclark64022c12016-05-27 05:13:26 -0700134 int counter = -1;
Chris Daltonde500372020-05-05 15:06:30 -0600135 SkPoint chop[7];
136 SkConic conicChop[2];
137 for (auto [verb, pts, w] : SkPathPriv::Iterate(*path)) {
caryclark64022c12016-05-27 05:13:26 -0700138 if (++counter == index) {
139 switch (verb) {
Chris Daltonde500372020-05-05 15:06:30 -0600140 case SkPathVerb::kLine:
caryclark64022c12016-05-27 05:13:26 -0700141 result.lineTo((pts[0].fX + pts[1].fX) / 2, (pts[0].fY + pts[1].fY) / 2);
142 break;
Chris Daltonde500372020-05-05 15:06:30 -0600143 case SkPathVerb::kQuad: {
caryclark64022c12016-05-27 05:13:26 -0700144 SkChopQuadAtHalf(pts, chop);
145 result.quadTo(chop[1], chop[2]);
Chris Daltonde500372020-05-05 15:06:30 -0600146 pts = chop + 2;
caryclark64022c12016-05-27 05:13:26 -0700147 } break;
Chris Daltonde500372020-05-05 15:06:30 -0600148 case SkPathVerb::kConic: {
caryclark64022c12016-05-27 05:13:26 -0700149 SkConic conic;
Chris Daltonde500372020-05-05 15:06:30 -0600150 conic.set(pts, *w);
151 if (!conic.chopAt(0.5f, conicChop)) {
caryclark414c4292016-09-26 11:03:54 -0700152 return;
153 }
Chris Daltonde500372020-05-05 15:06:30 -0600154 result.conicTo(conicChop[0].fPts[1], conicChop[0].fPts[2], conicChop[0].fW);
155 pts = conicChop[1].fPts;
156 w = &conicChop[1].fW;
caryclark64022c12016-05-27 05:13:26 -0700157 } break;
Chris Daltonde500372020-05-05 15:06:30 -0600158 case SkPathVerb::kCubic: {
caryclark64022c12016-05-27 05:13:26 -0700159 SkChopCubicAtHalf(pts, chop);
160 result.cubicTo(chop[1], chop[2], chop[3]);
Chris Daltonde500372020-05-05 15:06:30 -0600161 pts = chop + 3;
caryclark64022c12016-05-27 05:13:26 -0700162 } break;
Chris Daltonde500372020-05-05 15:06:30 -0600163 case SkPathVerb::kClose: {
caryclark64022c12016-05-27 05:13:26 -0700164 result.lineTo((lastPt.fX + firstPt.fX) / 2, (lastPt.fY + firstPt.fY) / 2);
165 } break;
166 default:
167 SkASSERT(0);
168 }
caryclark64022c12016-05-27 05:13:26 -0700169 }
170 switch (verb) {
Chris Daltonde500372020-05-05 15:06:30 -0600171 case SkPathVerb::kMove:
caryclark64022c12016-05-27 05:13:26 -0700172 result.moveTo(firstPt = pts[0]);
173 break;
Chris Daltonde500372020-05-05 15:06:30 -0600174 case SkPathVerb::kLine:
caryclark64022c12016-05-27 05:13:26 -0700175 result.lineTo(lastPt = pts[1]);
176 break;
Chris Daltonde500372020-05-05 15:06:30 -0600177 case SkPathVerb::kQuad:
caryclark64022c12016-05-27 05:13:26 -0700178 result.quadTo(pts[1], lastPt = pts[2]);
179 break;
Chris Daltonde500372020-05-05 15:06:30 -0600180 case SkPathVerb::kConic:
181 result.conicTo(pts[1], lastPt = pts[2], *w);
caryclark64022c12016-05-27 05:13:26 -0700182 break;
Chris Daltonde500372020-05-05 15:06:30 -0600183 case SkPathVerb::kCubic:
caryclark64022c12016-05-27 05:13:26 -0700184 result.cubicTo(pts[1], pts[2], lastPt = pts[3]);
185 break;
Chris Daltonde500372020-05-05 15:06:30 -0600186 case SkPathVerb::kClose:
caryclark64022c12016-05-27 05:13:26 -0700187 result.close();
188 break;
caryclark64022c12016-05-27 05:13:26 -0700189 default:
190 SkASSERT(0);
191 }
192 }
193 *path = result;
194}
195
196static void delete_path_segment(int index, SkPath* path) {
197 SkPath result;
caryclark64022c12016-05-27 05:13:26 -0700198 int counter = -1;
Chris Daltonde500372020-05-05 15:06:30 -0600199 for (auto [verb, pts, w] : SkPathPriv::Iterate(*path)) {
caryclark64022c12016-05-27 05:13:26 -0700200 if (++counter == index) {
201 continue;
202 }
203 switch (verb) {
Chris Daltonde500372020-05-05 15:06:30 -0600204 case SkPathVerb::kMove:
caryclark64022c12016-05-27 05:13:26 -0700205 result.moveTo(pts[0]);
206 break;
Chris Daltonde500372020-05-05 15:06:30 -0600207 case SkPathVerb::kLine:
caryclark64022c12016-05-27 05:13:26 -0700208 result.lineTo(pts[1]);
209 break;
Chris Daltonde500372020-05-05 15:06:30 -0600210 case SkPathVerb::kQuad:
caryclark64022c12016-05-27 05:13:26 -0700211 result.quadTo(pts[1], pts[2]);
212 break;
Chris Daltonde500372020-05-05 15:06:30 -0600213 case SkPathVerb::kConic:
214 result.conicTo(pts[1], pts[2], *w);
caryclark64022c12016-05-27 05:13:26 -0700215 break;
Chris Daltonde500372020-05-05 15:06:30 -0600216 case SkPathVerb::kCubic:
caryclark64022c12016-05-27 05:13:26 -0700217 result.cubicTo(pts[1], pts[2], pts[3]);
218 break;
Chris Daltonde500372020-05-05 15:06:30 -0600219 case SkPathVerb::kClose:
caryclark64022c12016-05-27 05:13:26 -0700220 result.close();
221 break;
caryclark64022c12016-05-27 05:13:26 -0700222 default:
223 SkASSERT(0);
224 }
225 }
226 *path = result;
227}
228
229static void set_path_weight(int index, SkScalar w, SkPath* path) {
230 SkPath result;
231 SkPoint pts[4];
232 SkPath::Verb verb;
233 SkPath::Iter iter(*path, true);
234 int counter = -1;
235 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
236 ++counter;
237 switch (verb) {
238 case SkPath::kMove_Verb:
239 result.moveTo(pts[0]);
240 break;
241 case SkPath::kLine_Verb:
242 result.lineTo(pts[1]);
243 break;
244 case SkPath::kQuad_Verb:
245 result.quadTo(pts[1], pts[2]);
246 break;
247 case SkPath::kConic_Verb:
248 result.conicTo(pts[1], pts[2], counter == index ? w : iter.conicWeight());
249 break;
250 case SkPath::kCubic_Verb:
251 result.cubicTo(pts[1], pts[2], pts[3]);
252 break;
253 case SkPath::kClose_Verb:
254 result.close();
255 break;
256 case SkPath::kDone_Verb:
257 break;
258 default:
259 SkASSERT(0);
260 }
261 }
262 *path = result;
263}
264
265static void set_path_verb(int index, SkPath::Verb v, SkPath* path, SkScalar w) {
266 SkASSERT(SkPath::kLine_Verb <= v && v <= SkPath::kCubic_Verb);
267 SkPath result;
268 SkPoint pts[4];
269 SkPath::Verb verb;
270 SkPath::Iter iter(*path, true);
271 int counter = -1;
272 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
273 SkScalar weight = verb == SkPath::kConic_Verb ? iter.conicWeight() : 1;
274 if (++counter == index && v != verb) {
275 SkASSERT(SkPath::kLine_Verb <= verb && verb <= SkPath::kCubic_Verb);
276 switch (verb) {
277 case SkPath::kLine_Verb:
278 switch (v) {
279 case SkPath::kConic_Verb:
280 weight = w;
John Stiles30212b72020-06-11 17:55:07 -0400281 [[fallthrough]];
caryclark64022c12016-05-27 05:13:26 -0700282 case SkPath::kQuad_Verb:
283 pts[2] = pts[1];
284 pts[1].fX = (pts[0].fX + pts[2].fX) / 2;
285 pts[1].fY = (pts[0].fY + pts[2].fY) / 2;
286 break;
287 case SkPath::kCubic_Verb:
288 pts[3] = pts[1];
289 pts[1].fX = (pts[0].fX * 2 + pts[3].fX) / 3;
290 pts[1].fY = (pts[0].fY * 2 + pts[3].fY) / 3;
291 pts[2].fX = (pts[0].fX + pts[3].fX * 2) / 3;
292 pts[2].fY = (pts[0].fY + pts[3].fY * 2) / 3;
293 break;
294 default:
295 SkASSERT(0);
296 break;
297 }
298 break;
299 case SkPath::kQuad_Verb:
300 case SkPath::kConic_Verb:
301 switch (v) {
302 case SkPath::kLine_Verb:
303 pts[1] = pts[2];
304 break;
305 case SkPath::kConic_Verb:
306 weight = w;
John Stiles30212b72020-06-11 17:55:07 -0400307 [[fallthrough]];
caryclark64022c12016-05-27 05:13:26 -0700308 case SkPath::kQuad_Verb:
309 break;
310 case SkPath::kCubic_Verb: {
311 SkDQuad dQuad;
312 dQuad.set(pts);
313 SkDCubic dCubic = dQuad.debugToCubic();
314 pts[3] = pts[2];
315 pts[1] = dCubic[1].asSkPoint();
316 pts[2] = dCubic[2].asSkPoint();
317 } break;
318 default:
319 SkASSERT(0);
320 break;
321 }
322 break;
323 case SkPath::kCubic_Verb:
324 switch (v) {
325 case SkPath::kLine_Verb:
326 pts[1] = pts[3];
327 break;
328 case SkPath::kConic_Verb:
329 weight = w;
John Stiles30212b72020-06-11 17:55:07 -0400330 [[fallthrough]];
caryclark64022c12016-05-27 05:13:26 -0700331 case SkPath::kQuad_Verb: {
332 SkDCubic dCubic;
333 dCubic.set(pts);
334 SkDQuad dQuad = dCubic.toQuad();
335 pts[1] = dQuad[1].asSkPoint();
336 pts[2] = pts[3];
337 } break;
338 default:
339 SkASSERT(0);
340 break;
341 }
342 break;
343 default:
344 SkASSERT(0);
345 break;
346 }
347 verb = v;
348 }
349 switch (verb) {
350 case SkPath::kMove_Verb:
351 result.moveTo(pts[0]);
352 break;
353 case SkPath::kLine_Verb:
354 result.lineTo(pts[1]);
355 break;
356 case SkPath::kQuad_Verb:
357 result.quadTo(pts[1], pts[2]);
358 break;
359 case SkPath::kConic_Verb:
360 result.conicTo(pts[1], pts[2], weight);
361 break;
362 case SkPath::kCubic_Verb:
363 result.cubicTo(pts[1], pts[2], pts[3]);
364 break;
365 case SkPath::kClose_Verb:
366 result.close();
367 break;
368 default:
369 SkASSERT(0);
370 break;
371 }
372 }
373 *path = result;
374}
375
376static void add_to_map(SkScalar coverage, int x, int y, uint8_t* distanceMap, int w, int h) {
377 int byteCoverage = (int) (coverage * 256);
378 if (byteCoverage < 0) {
379 byteCoverage = 0;
380 } else if (byteCoverage > 255) {
381 byteCoverage = 255;
382 }
383 SkASSERT(x < w);
384 SkASSERT(y < h);
Brian Osman788b9162020-02-07 10:36:46 -0500385 distanceMap[y * w + x] = std::max(distanceMap[y * w + x], (uint8_t) byteCoverage);
caryclark64022c12016-05-27 05:13:26 -0700386}
387
388static void filter_coverage(const uint8_t* map, int len, uint8_t min, uint8_t max,
389 uint8_t* filter) {
390 for (int index = 0; index < len; ++index) {
391 uint8_t in = map[index];
392 filter[index] = in < min ? 0 : max < in ? 0 : in;
393 }
394}
395
396static void construct_path(SkPath& path) {
397 path.reset();
398 path.moveTo(442, 101.5f);
399 path.quadTo(413.5f, 691, 772, 514);
400 path.lineTo(346, 721.5f);
401 path.lineTo(154, 209);
402 path.lineTo(442, 101.5f);
403 path.close();
404}
405
406struct ButtonPaints {
407 static const int kMaxStateCount = 3;
408 SkPaint fDisabled;
409 SkPaint fStates[kMaxStateCount];
Mike Reedb579f072019-01-03 15:45:53 -0500410 SkFont fLabelFont;
caryclark64022c12016-05-27 05:13:26 -0700411
412 ButtonPaints() {
413 fStates[0].setAntiAlias(true);
414 fStates[0].setStyle(SkPaint::kStroke_Style);
415 fStates[0].setColor(0xFF3F0000);
416 fStates[1] = fStates[0];
417 fStates[1].setStrokeWidth(3);
418 fStates[2] = fStates[1];
419 fStates[2].setColor(0xFFcf0000);
Mike Reedb579f072019-01-03 15:45:53 -0500420 fLabelFont.setSize(25.0f);
caryclark64022c12016-05-27 05:13:26 -0700421 }
422};
423
424struct Button {
425 SkRect fBounds;
426 int fStateCount;
427 int fState;
428 char fLabel;
429 bool fVisible;
430
431 Button(char label) {
432 fStateCount = 2;
433 fState = 0;
434 fLabel = label;
435 fVisible = false;
436 }
437
438 Button(char label, int stateCount) {
439 SkASSERT(stateCount <= ButtonPaints::kMaxStateCount);
440 fStateCount = stateCount;
441 fState = 0;
442 fLabel = label;
443 fVisible = false;
444 }
445
446 bool contains(const SkRect& rect) {
447 return fVisible && fBounds.contains(rect);
448 }
449
450 bool enabled() {
451 return SkToBool(fState);
452 }
453
454 void draw(SkCanvas* canvas, const ButtonPaints& paints) {
455 if (!fVisible) {
456 return;
457 }
458 canvas->drawRect(fBounds, paints.fStates[fState]);
Ben Wagner51e15a62019-05-07 15:38:46 -0400459 SkTextUtils::Draw(canvas, &fLabel, 1, SkTextEncoding::kUTF8, fBounds.centerX(), fBounds.fBottom - 5,
Mike Reedb579f072019-01-03 15:45:53 -0500460 paints.fLabelFont, SkPaint(), SkTextUtils::kCenter_Align);
caryclark64022c12016-05-27 05:13:26 -0700461 }
462
463 void toggle() {
464 if (++fState == fStateCount) {
465 fState = 0;
466 }
467 }
468
469 void setEnabled(bool enabled) {
470 fState = (int) enabled;
471 }
472};
473
474struct ControlPaints {
475 SkPaint fOutline;
476 SkPaint fIndicator;
477 SkPaint fFill;
478 SkPaint fLabel;
479 SkPaint fValue;
480
Mike Reed89126e42019-01-03 12:59:14 -0500481 SkFont fLabelFont;
482 SkFont fValueFont;
483
caryclark64022c12016-05-27 05:13:26 -0700484 ControlPaints() {
485 fOutline.setAntiAlias(true);
486 fOutline.setStyle(SkPaint::kStroke_Style);
487 fIndicator = fOutline;
488 fIndicator.setColor(SK_ColorRED);
489 fFill.setAntiAlias(true);
490 fFill.setColor(0x7fff0000);
491 fLabel.setAntiAlias(true);
Mike Reed89126e42019-01-03 12:59:14 -0500492 fLabelFont.setSize(13.0f);
caryclark64022c12016-05-27 05:13:26 -0700493 fValue.setAntiAlias(true);
Mike Reed89126e42019-01-03 12:59:14 -0500494 fValueFont.setSize(11.0f);
caryclark64022c12016-05-27 05:13:26 -0700495 }
496};
497
498struct UniControl {
499 SkString fName;
500 SkRect fBounds;
501 SkScalar fMin;
502 SkScalar fMax;
503 SkScalar fValLo;
504 SkScalar fYLo;
505 bool fVisible;
506
507 UniControl(const char* name, SkScalar min, SkScalar max) {
508 fName = name;
509 fValLo = fMin = min;
510 fMax = max;
511 fVisible = false;
512
513 }
514
515 virtual ~UniControl() {}
516
517 bool contains(const SkRect& rect) {
518 return fVisible && fBounds.contains(rect);
519 }
520
521 virtual void draw(SkCanvas* canvas, const ControlPaints& paints) {
522 if (!fVisible) {
523 return;
524 }
525 canvas->drawRect(fBounds, paints.fOutline);
526 fYLo = fBounds.fTop + (fValLo - fMin) * fBounds.height() / (fMax - fMin);
527 canvas->drawLine(fBounds.fLeft - 5, fYLo, fBounds.fRight + 5, fYLo, paints.fIndicator);
528 SkString label;
529 label.printf("%0.3g", fValLo);
Hal Canary89a644b2019-01-07 09:36:09 -0500530 canvas->drawString(label, fBounds.fLeft + 5, fYLo - 5, paints.fValueFont, paints.fValue);
531 canvas->drawString(fName, fBounds.fLeft, fBounds.bottom() + 11, paints.fLabelFont,
532 paints.fLabel);
caryclark64022c12016-05-27 05:13:26 -0700533 }
534};
535
536struct BiControl : public UniControl {
537 SkScalar fValHi;
538
Ben Wagner63fd7602017-10-09 15:45:33 -0400539 BiControl(const char* name, SkScalar min, SkScalar max)
caryclark64022c12016-05-27 05:13:26 -0700540 : UniControl(name, min, max)
541 , fValHi(fMax) {
542 }
Ben Wagner63fd7602017-10-09 15:45:33 -0400543
caryclark64022c12016-05-27 05:13:26 -0700544 virtual ~BiControl() {}
545
546 virtual void draw(SkCanvas* canvas, const ControlPaints& paints) {
547 UniControl::draw(canvas, paints);
548 if (!fVisible || fValHi == fValLo) {
549 return;
550 }
551 SkScalar yPos = fBounds.fTop + (fValHi - fMin) * fBounds.height() / (fMax - fMin);
552 canvas->drawLine(fBounds.fLeft - 5, yPos, fBounds.fRight + 5, yPos, paints.fIndicator);
553 SkString label;
554 label.printf("%0.3g", fValHi);
555 if (yPos < fYLo + 10) {
556 yPos = fYLo + 10;
557 }
Hal Canary4484b8f2019-01-08 14:00:08 -0500558 canvas->drawString(label, fBounds.fLeft + 5, yPos - 5, paints.fValueFont, paints.fValue);
caryclark64022c12016-05-27 05:13:26 -0700559 SkRect fill = { fBounds.fLeft, fYLo, fBounds.fRight, yPos };
560 canvas->drawRect(fill, paints.fFill);
561 }
562};
563
564
Ben Wagnerb2c4ea62018-08-08 11:36:17 -0400565class MyClick : public Sample::Click {
caryclark64022c12016-05-27 05:13:26 -0700566public:
567 enum ClickType {
568 kInvalidType = -1,
569 kPtType,
570 kVerbType,
571 kControlType,
572 kPathType,
573 } fType;
574
575 enum ControlType {
576 kInvalidControl = -1,
577 kFirstControl,
578 kFilterControl = kFirstControl,
579 kResControl,
580 kWeightControl,
581 kWidthControl,
582 kLastControl = kWidthControl,
583 kFirstButton,
584 kCubicButton = kFirstButton,
585 kConicButton,
586 kQuadButton,
587 kLineButton,
588 kLastVerbButton = kLineButton,
589 kAddButton,
590 kDeleteButton,
591 kInOutButton,
592 kFillButton,
593 kSkeletonButton,
594 kFilterButton,
595 kBisectButton,
596 kJoinButton,
597 kLastButton = kJoinButton,
598 kPathMove,
599 } fControl;
600
601 SkPath::Verb fVerb;
602 SkScalar fWeight;
603
Hal Canaryfcf63592019-07-12 11:32:43 -0400604 MyClick(ClickType type, ControlType control)
605 : fType(type)
caryclark64022c12016-05-27 05:13:26 -0700606 , fControl(control)
607 , fVerb((SkPath::Verb) -1)
608 , fWeight(1) {
609 }
610
Hal Canaryfcf63592019-07-12 11:32:43 -0400611 MyClick(ClickType type, int index)
612 : fType(type)
caryclark64022c12016-05-27 05:13:26 -0700613 , fControl((ControlType) index)
614 , fVerb((SkPath::Verb) -1)
615 , fWeight(1) {
616 }
617
Hal Canaryfcf63592019-07-12 11:32:43 -0400618 MyClick(ClickType type, int index, SkPath::Verb verb, SkScalar weight)
619 : fType(type)
caryclark64022c12016-05-27 05:13:26 -0700620 , fControl((ControlType) index)
621 , fVerb(verb)
622 , fWeight(weight) {
623 }
624
625 bool isButton() {
626 return kFirstButton <= fControl && fControl <= kLastButton;
627 }
628
629 int ptHit() const {
630 SkASSERT(fType == kPtType);
631 return (int) fControl;
632 }
633
634 int verbHit() const {
635 SkASSERT(fType == kVerbType);
636 return (int) fControl;
637 }
638};
639
640enum {
641 kControlCount = MyClick::kLastControl - MyClick::kFirstControl + 1,
642};
643
644static struct ControlPair {
645 UniControl* fControl;
646 MyClick::ControlType fControlType;
647} kControlList[kControlCount];
648
649enum {
650 kButtonCount = MyClick::kLastButton - MyClick::kFirstButton + 1,
651 kVerbCount = MyClick::kLastVerbButton - MyClick::kFirstButton + 1,
652};
653
654static struct ButtonPair {
655 Button* fButton;
656 MyClick::ControlType fButtonType;
657} kButtonList[kButtonCount];
658
659static void enable_verb_button(MyClick::ControlType type) {
660 for (int index = 0; index < kButtonCount; ++index) {
661 MyClick::ControlType testType = kButtonList[index].fButtonType;
662 if (MyClick::kFirstButton <= testType && testType <= MyClick::kLastVerbButton) {
663 Button* button = kButtonList[index].fButton;
664 button->setEnabled(testType == type);
665 }
666 }
667}
668
669struct Stroke;
670
671struct Active {
672 Active* fNext;
673 Stroke* fParent;
674 SkScalar fStart;
675 SkScalar fEnd;
676
677 void reset() {
Ben Wagnera93a14a2017-08-28 10:34:05 -0400678 fNext = nullptr;
caryclark64022c12016-05-27 05:13:26 -0700679 fStart = 0;
680 fEnd = 1;
681 }
682};
683
684struct Stroke {
685 SkPath fPath;
686 Active fActive;
687 bool fInner;
688
689 void reset() {
690 fPath.reset();
691 fActive.reset();
692 }
693};
694
695struct PathUndo {
696 SkPath fPath;
Hal Canary8918d532019-07-12 10:04:14 -0400697 std::unique_ptr<PathUndo> fNext;
caryclark64022c12016-05-27 05:13:26 -0700698};
699
Ben Wagnerb2c4ea62018-08-08 11:36:17 -0400700class AAGeometryView : public Sample {
caryclark64022c12016-05-27 05:13:26 -0700701 SkPaint fActivePaint;
702 SkPaint fComplexPaint;
703 SkPaint fCoveragePaint;
Mike Reedb579f072019-01-03 15:45:53 -0500704 SkFont fLegendLeftFont;
705 SkFont fLegendRightFont;
caryclark64022c12016-05-27 05:13:26 -0700706 SkPaint fPointPaint;
707 SkPaint fSkeletonPaint;
708 SkPaint fLightSkeletonPaint;
709 SkPath fPath;
710 ControlPaints fControlPaints;
711 UniControl fResControl;
712 UniControl fWeightControl;
713 UniControl fWidthControl;
714 BiControl fFilterControl;
715 ButtonPaints fButtonPaints;
716 Button fCubicButton;
717 Button fConicButton;
718 Button fQuadButton;
719 Button fLineButton;
720 Button fAddButton;
721 Button fDeleteButton;
722 Button fFillButton;
723 Button fSkeletonButton;
724 Button fFilterButton;
725 Button fBisectButton;
726 Button fJoinButton;
727 Button fInOutButton;
728 SkTArray<Stroke> fStrokes;
Hal Canary8918d532019-07-12 10:04:14 -0400729 std::unique_ptr<PathUndo> fUndo;
caryclark64022c12016-05-27 05:13:26 -0700730 int fActivePt;
731 int fActiveVerb;
732 bool fHandlePathMove;
733 bool fShowLegend;
734 bool fHideAll;
Chris Dalton08d1a252017-10-20 11:46:47 -0600735 const int kHitToleranace = 25;
caryclark64022c12016-05-27 05:13:26 -0700736
737public:
738
Ben Wagner63fd7602017-10-09 15:45:33 -0400739 AAGeometryView()
caryclark64022c12016-05-27 05:13:26 -0700740 : fResControl("error", 0, 10)
741 , fWeightControl("weight", 0, 5)
742 , fWidthControl("width", FLT_EPSILON, 100)
743 , fFilterControl("filter", 0, 255)
744 , fCubicButton('C')
745 , fConicButton('K')
746 , fQuadButton('Q')
747 , fLineButton('L')
748 , fAddButton('+')
749 , fDeleteButton('x')
750 , fFillButton('p')
751 , fSkeletonButton('s')
752 , fFilterButton('f', 3)
753 , fBisectButton('b')
754 , fJoinButton('j')
755 , fInOutButton('|')
caryclark64022c12016-05-27 05:13:26 -0700756 , fActivePt(-1)
757 , fActiveVerb(-1)
758 , fHandlePathMove(true)
759 , fShowLegend(false)
760 , fHideAll(false)
761 {
762 fCoveragePaint.setAntiAlias(true);
763 fCoveragePaint.setColor(SK_ColorBLUE);
764 SkPaint strokePaint;
765 strokePaint.setAntiAlias(true);
766 strokePaint.setStyle(SkPaint::kStroke_Style);
767 fPointPaint = strokePaint;
768 fPointPaint.setColor(0x99ee3300);
769 fSkeletonPaint = strokePaint;
770 fSkeletonPaint.setColor(SK_ColorRED);
771 fLightSkeletonPaint = fSkeletonPaint;
772 fLightSkeletonPaint.setColor(0xFFFF7f7f);
773 fActivePaint = strokePaint;
774 fActivePaint.setColor(0x99ee3300);
775 fActivePaint.setStrokeWidth(5);
776 fComplexPaint = fActivePaint;
777 fComplexPaint.setColor(SK_ColorBLUE);
Mike Reedb579f072019-01-03 15:45:53 -0500778 fLegendLeftFont.setSize(13);
779 fLegendRightFont = fLegendLeftFont;
caryclark64022c12016-05-27 05:13:26 -0700780 construct_path(fPath);
781 fFillButton.fVisible = fSkeletonButton.fVisible = fFilterButton.fVisible
782 = fBisectButton.fVisible = fJoinButton.fVisible = fInOutButton.fVisible = true;
783 fSkeletonButton.setEnabled(true);
784 fInOutButton.setEnabled(true);
785 fJoinButton.setEnabled(true);
786 fFilterControl.fValLo = 120;
787 fFilterControl.fValHi = 141;
788 fFilterControl.fVisible = fFilterButton.fState == 2;
789 fResControl.fValLo = 5;
790 fResControl.fVisible = true;
791 fWidthControl.fValLo = 50;
792 fWidthControl.fVisible = true;
793 init_controlList();
794 init_buttonList();
795 }
796
Hal Canary8918d532019-07-12 10:04:14 -0400797 ~AAGeometryView() override {
798 // Free linked list without deep recursion.
799 std::unique_ptr<PathUndo> undo = std::move(fUndo);
800 while (undo) {
801 undo = std::move(undo->fNext);
802 }
803 }
804
caryclark64022c12016-05-27 05:13:26 -0700805 bool constructPath() {
806 construct_path(fPath);
caryclark64022c12016-05-27 05:13:26 -0700807 return true;
808 }
809
Hal Canaryb1f411a2019-08-29 10:39:22 -0400810 void savePath(skui::InputState state) {
811 if (state != skui::InputState::kDown) {
caryclark64022c12016-05-27 05:13:26 -0700812 return;
813 }
814 if (fUndo && fUndo->fPath == fPath) {
815 return;
816 }
Hal Canary8918d532019-07-12 10:04:14 -0400817 std::unique_ptr<PathUndo> undo(new PathUndo);
caryclark64022c12016-05-27 05:13:26 -0700818 undo->fPath = fPath;
Hal Canary8918d532019-07-12 10:04:14 -0400819 undo->fNext = std::move(fUndo);
820 fUndo = std::move(undo);
caryclark64022c12016-05-27 05:13:26 -0700821 }
822
823 bool undo() {
824 if (!fUndo) {
825 return false;
826 }
Hal Canary8918d532019-07-12 10:04:14 -0400827 fPath = std::move(fUndo->fPath);
828 fUndo = std::move(fUndo->fNext);
caryclark64022c12016-05-27 05:13:26 -0700829 validatePath();
caryclark64022c12016-05-27 05:13:26 -0700830 return true;
831 }
832
Hal Canary8918d532019-07-12 10:04:14 -0400833 void validatePath() {}
caryclark64022c12016-05-27 05:13:26 -0700834
835 void set_controlList(int index, UniControl* control, MyClick::ControlType type) {
836 kControlList[index].fControl = control;
837 kControlList[index].fControlType = type;
838 }
839
840 #define SET_CONTROL(Name) set_controlList(index++, &f##Name##Control, \
841 MyClick::k##Name##Control)
842
843 bool hideAll() {
844 fHideAll ^= true;
caryclark64022c12016-05-27 05:13:26 -0700845 return true;
846 }
847
848 void init_controlList() {
849 int index = 0;
850 SET_CONTROL(Width);
851 SET_CONTROL(Res);
852 SET_CONTROL(Filter);
853 SET_CONTROL(Weight);
Brian Osman16adfa32016-10-18 14:42:44 -0400854 }
Ben Wagner63fd7602017-10-09 15:45:33 -0400855
caryclark64022c12016-05-27 05:13:26 -0700856 #undef SET_CONTROL
857
858 void set_buttonList(int index, Button* button, MyClick::ControlType type) {
859 kButtonList[index].fButton = button;
860 kButtonList[index].fButtonType = type;
861 }
862
863 #define SET_BUTTON(Name) set_buttonList(index++, &f##Name##Button, \
864 MyClick::k##Name##Button)
865
866 void init_buttonList() {
867 int index = 0;
868 SET_BUTTON(Fill);
869 SET_BUTTON(Skeleton);
870 SET_BUTTON(Filter);
871 SET_BUTTON(Bisect);
872 SET_BUTTON(Join);
873 SET_BUTTON(InOut);
874 SET_BUTTON(Cubic);
875 SET_BUTTON(Conic);
876 SET_BUTTON(Quad);
877 SET_BUTTON(Line);
878 SET_BUTTON(Add);
879 SET_BUTTON(Delete);
880 }
881
882 #undef SET_BUTTON
883
Hal Canary8a027312019-07-03 10:55:44 -0400884 SkString name() override { return SkString("AAGeometry"); }
885
Hal Canary6cc65e12019-07-03 15:53:04 -0400886 bool onChar(SkUnichar) override;
Ben Wagner63fd7602017-10-09 15:45:33 -0400887
caryclark64022c12016-05-27 05:13:26 -0700888 void onSizeChange() override {
889 setControlButtonsPos();
890 this->INHERITED::onSizeChange();
891 }
892
893 bool pathDump() {
894 fPath.dump();
895 return true;
896 }
897
898 bool scaleDown() {
899 SkMatrix matrix;
900 SkRect bounds = fPath.getBounds();
901 matrix.setScale(1.f / 1.5f, 1.f / 1.5f, bounds.centerX(), bounds.centerY());
902 fPath.transform(matrix);
903 validatePath();
caryclark64022c12016-05-27 05:13:26 -0700904 return true;
905 }
906
907 bool scaleToFit() {
908 SkMatrix matrix;
909 SkRect bounds = fPath.getBounds();
Brian Osman788b9162020-02-07 10:36:46 -0500910 SkScalar scale = std::min(this->width() / bounds.width(), this->height() / bounds.height())
caryclark64022c12016-05-27 05:13:26 -0700911 * 0.8f;
912 matrix.setScale(scale, scale, bounds.centerX(), bounds.centerY());
913 fPath.transform(matrix);
914 bounds = fPath.getBounds();
915 SkScalar offsetX = (this->width() - bounds.width()) / 2 - bounds.fLeft;
916 SkScalar offsetY = (this->height() - bounds.height()) / 2 - bounds.fTop;
917 fPath.offset(offsetX, offsetY);
918 validatePath();
caryclark64022c12016-05-27 05:13:26 -0700919 return true;
920 }
921
922 bool scaleUp() {
923 SkMatrix matrix;
924 SkRect bounds = fPath.getBounds();
925 matrix.setScale(1.5f, 1.5f, bounds.centerX(), bounds.centerY());
926 fPath.transform(matrix);
927 validatePath();
caryclark64022c12016-05-27 05:13:26 -0700928 return true;
929 }
930
931 void setControlButtonsPos() {
932 SkScalar widthOffset = this->width() - 100;
933 for (int index = 0; index < kControlCount; ++index) {
934 if (kControlList[index].fControl->fVisible) {
935 kControlList[index].fControl->fBounds.setXYWH(widthOffset, 30, 30, 400);
936 widthOffset -= 50;
937 }
938 }
939 SkScalar buttonOffset = 0;
940 for (int index = 0; index < kButtonCount; ++index) {
941 kButtonList[index].fButton->fBounds.setXYWH(this->width() - 50,
942 buttonOffset += 50, 30, 30);
943 }
944 }
945
946 bool showLegend() {
947 fShowLegend ^= true;
caryclark64022c12016-05-27 05:13:26 -0700948 return true;
949 }
950
951 void draw_bisect(SkCanvas* canvas, const SkVector& lastVector, const SkVector& vector,
952 const SkPoint& pt) {
953 SkVector lastV = lastVector;
954 SkScalar lastLen = lastVector.length();
955 SkVector nextV = vector;
956 SkScalar nextLen = vector.length();
957 if (lastLen < nextLen) {
958 lastV.setLength(nextLen);
959 } else {
960 nextV.setLength(lastLen);
961 }
962
963 SkVector bisect = { (lastV.fX + nextV.fX) / 2, (lastV.fY + nextV.fY) / 2 };
964 bisect.setLength(fWidthControl.fValLo * 2);
965 if (fBisectButton.enabled()) {
Hal Canary23e474c2017-05-15 13:35:35 -0400966 canvas->drawLine(pt, pt + bisect, fSkeletonPaint);
caryclark64022c12016-05-27 05:13:26 -0700967 }
968 lastV.setLength(fWidthControl.fValLo);
969 if (fBisectButton.enabled()) {
Hal Canary23e474c2017-05-15 13:35:35 -0400970 canvas->drawLine(pt, {pt.fX - lastV.fY, pt.fY + lastV.fX}, fSkeletonPaint);
caryclark64022c12016-05-27 05:13:26 -0700971 }
972 nextV.setLength(fWidthControl.fValLo);
973 if (fBisectButton.enabled()) {
Hal Canary23e474c2017-05-15 13:35:35 -0400974 canvas->drawLine(pt, {pt.fX + nextV.fY, pt.fY - nextV.fX}, fSkeletonPaint);
caryclark64022c12016-05-27 05:13:26 -0700975 }
976 if (fJoinButton.enabled()) {
977 SkScalar r = fWidthControl.fValLo;
978 SkRect oval = { pt.fX - r, pt.fY - r, pt.fX + r, pt.fY + r};
979 SkScalar startAngle = SkScalarATan2(lastV.fX, -lastV.fY) * 180.f / SK_ScalarPI;
980 SkScalar endAngle = SkScalarATan2(-nextV.fX, nextV.fY) * 180.f / SK_ScalarPI;
981 if (endAngle > startAngle) {
982 canvas->drawArc(oval, startAngle, endAngle - startAngle, false, fSkeletonPaint);
983 } else {
984 canvas->drawArc(oval, startAngle, 360 - (startAngle - endAngle), false,
985 fSkeletonPaint);
986 }
987 }
988 }
989
990 void draw_bisects(SkCanvas* canvas, bool activeOnly) {
991 SkVector firstVector, lastVector, nextLast, vector;
992 SkPoint pts[4];
993 SkPoint firstPt = { 0, 0 }; // init to avoid warning;
994 SkPath::Verb verb;
995 SkPath::Iter iter(fPath, true);
996 bool foundFirst = false;
997 int counter = -1;
998 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
999 ++counter;
1000 if (activeOnly && counter != fActiveVerb && counter - 1 != fActiveVerb
1001 && counter + 1 != fActiveVerb
1002 && (fActiveVerb != 1 || counter != fPath.countVerbs())) {
1003 continue;
1004 }
1005 switch (verb) {
1006 case SkPath::kLine_Verb:
1007 nextLast = pts[0] - pts[1];
1008 vector = pts[1] - pts[0];
1009 break;
1010 case SkPath::kQuad_Verb: {
1011 nextLast = pts[1] - pts[2];
1012 if (SkScalarNearlyZero(nextLast.length())) {
1013 nextLast = pts[0] - pts[2];
1014 }
1015 vector = pts[1] - pts[0];
1016 if (SkScalarNearlyZero(vector.length())) {
1017 vector = pts[2] - pts[0];
1018 }
1019 if (!fBisectButton.enabled()) {
1020 break;
1021 }
1022 SkScalar t = SkFindQuadMaxCurvature(pts);
1023 if (0 < t && t < 1) {
1024 SkPoint maxPt = SkEvalQuadAt(pts, t);
1025 SkVector tangent = SkEvalQuadTangentAt(pts, t);
1026 tangent.setLength(fWidthControl.fValLo * 2);
Hal Canary23e474c2017-05-15 13:35:35 -04001027 canvas->drawLine(maxPt, {maxPt.fX + tangent.fY, maxPt.fY - tangent.fX},
1028 fSkeletonPaint);
caryclark64022c12016-05-27 05:13:26 -07001029 }
1030 } break;
1031 case SkPath::kConic_Verb:
1032 nextLast = pts[1] - pts[2];
1033 if (SkScalarNearlyZero(nextLast.length())) {
1034 nextLast = pts[0] - pts[2];
1035 }
1036 vector = pts[1] - pts[0];
1037 if (SkScalarNearlyZero(vector.length())) {
1038 vector = pts[2] - pts[0];
1039 }
1040 if (!fBisectButton.enabled()) {
1041 break;
1042 }
1043 // FIXME : need max curvature or equivalent here
1044 break;
1045 case SkPath::kCubic_Verb: {
1046 nextLast = pts[2] - pts[3];
1047 if (SkScalarNearlyZero(nextLast.length())) {
1048 nextLast = pts[1] - pts[3];
1049 if (SkScalarNearlyZero(nextLast.length())) {
1050 nextLast = pts[0] - pts[3];
1051 }
1052 }
1053 vector = pts[0] - pts[1];
1054 if (SkScalarNearlyZero(vector.length())) {
1055 vector = pts[0] - pts[2];
1056 if (SkScalarNearlyZero(vector.length())) {
1057 vector = pts[0] - pts[3];
1058 }
1059 }
1060 if (!fBisectButton.enabled()) {
1061 break;
1062 }
1063 SkScalar tMax[2];
1064 int tMaxCount = SkFindCubicMaxCurvature(pts, tMax);
1065 for (int tIndex = 0; tIndex < tMaxCount; ++tIndex) {
1066 if (0 >= tMax[tIndex] || tMax[tIndex] >= 1) {
1067 continue;
1068 }
1069 SkPoint maxPt;
1070 SkVector tangent;
Ben Wagnera93a14a2017-08-28 10:34:05 -04001071 SkEvalCubicAt(pts, tMax[tIndex], &maxPt, &tangent, nullptr);
caryclark64022c12016-05-27 05:13:26 -07001072 tangent.setLength(fWidthControl.fValLo * 2);
Hal Canary23e474c2017-05-15 13:35:35 -04001073 canvas->drawLine(maxPt, {maxPt.fX + tangent.fY, maxPt.fY - tangent.fX},
1074 fSkeletonPaint);
caryclark64022c12016-05-27 05:13:26 -07001075 }
1076 } break;
1077 case SkPath::kClose_Verb:
1078 if (foundFirst) {
1079 draw_bisect(canvas, lastVector, firstVector, firstPt);
1080 foundFirst = false;
1081 }
1082 break;
Ben Wagner63fd7602017-10-09 15:45:33 -04001083 default:
caryclark64022c12016-05-27 05:13:26 -07001084 break;
1085 }
1086 if (SkPath::kLine_Verb <= verb && verb <= SkPath::kCubic_Verb) {
1087 if (!foundFirst) {
1088 firstPt = pts[0];
1089 firstVector = vector;
1090 foundFirst = true;
1091 } else {
1092 draw_bisect(canvas, lastVector, vector, pts[0]);
1093 }
1094 lastVector = nextLast;
1095 }
1096 }
1097 }
1098
1099 void draw_legend(SkCanvas* canvas);
1100
1101 void draw_segment(SkCanvas* canvas) {
1102 SkPoint pts[4];
1103 SkPath::Verb verb;
1104 SkPath::Iter iter(fPath, true);
1105 int counter = -1;
1106 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1107 if (++counter < fActiveVerb) {
1108 continue;
1109 }
1110 switch (verb) {
1111 case SkPath::kLine_Verb:
1112 canvas->drawPoints(SkCanvas::kLines_PointMode, 2, pts, fActivePaint);
1113 draw_points(canvas, pts, 2);
1114 break;
1115 case SkPath::kQuad_Verb: {
1116 SkPath qPath;
1117 qPath.moveTo(pts[0]);
1118 qPath.quadTo(pts[1], pts[2]);
1119 canvas->drawPath(qPath, fActivePaint);
1120 draw_points(canvas, pts, 3);
1121 } break;
1122 case SkPath::kConic_Verb: {
1123 SkPath conicPath;
1124 conicPath.moveTo(pts[0]);
1125 conicPath.conicTo(pts[1], pts[2], iter.conicWeight());
1126 canvas->drawPath(conicPath, fActivePaint);
1127 draw_points(canvas, pts, 3);
1128 } break;
1129 case SkPath::kCubic_Verb: {
Cary Clark7eb01e02016-12-08 14:36:32 -05001130 SkScalar loopT[3];
1131 int complex = SkDCubic::ComplexBreak(pts, loopT);
caryclark64022c12016-05-27 05:13:26 -07001132 SkPath cPath;
1133 cPath.moveTo(pts[0]);
1134 cPath.cubicTo(pts[1], pts[2], pts[3]);
1135 canvas->drawPath(cPath, complex ? fComplexPaint : fActivePaint);
1136 draw_points(canvas, pts, 4);
1137 } break;
Ben Wagner63fd7602017-10-09 15:45:33 -04001138 default:
caryclark64022c12016-05-27 05:13:26 -07001139 break;
1140 }
1141 return;
1142 }
1143 }
1144
1145 void draw_points(SkCanvas* canvas, SkPoint* points, int count) {
1146 for (int index = 0; index < count; ++index) {
1147 canvas->drawCircle(points[index].fX, points[index].fY, 10, fPointPaint);
1148 }
1149 }
1150
1151 int hittest_verb(SkPoint pt, SkPath::Verb* verbPtr, SkScalar* weight) {
1152 SkIntersections i;
1153 SkDLine hHit = {{{pt.fX - kHitToleranace, pt.fY }, {pt.fX + kHitToleranace, pt.fY}}};
1154 SkDLine vHit = {{{pt.fX, pt.fY - kHitToleranace }, {pt.fX, pt.fY + kHitToleranace}}};
1155 SkPoint pts[4];
1156 SkPath::Verb verb;
1157 SkPath::Iter iter(fPath, true);
1158 int counter = -1;
1159 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1160 ++counter;
1161 switch (verb) {
1162 case SkPath::kLine_Verb: {
1163 SkDLine line;
1164 line.set(pts);
1165 if (i.intersect(line, hHit) || i.intersect(line, vHit)) {
1166 *verbPtr = verb;
1167 *weight = 1;
1168 return counter;
1169 }
1170 } break;
1171 case SkPath::kQuad_Verb: {
1172 SkDQuad quad;
1173 quad.set(pts);
1174 if (i.intersect(quad, hHit) || i.intersect(quad, vHit)) {
1175 *verbPtr = verb;
1176 *weight = 1;
1177 return counter;
1178 }
1179 } break;
1180 case SkPath::kConic_Verb: {
1181 SkDConic conic;
1182 SkScalar w = iter.conicWeight();
1183 conic.set(pts, w);
1184 if (i.intersect(conic, hHit) || i.intersect(conic, vHit)) {
1185 *verbPtr = verb;
1186 *weight = w;
1187 return counter;
1188 }
1189 } break;
1190 case SkPath::kCubic_Verb: {
1191 SkDCubic cubic;
1192 cubic.set(pts);
1193 if (i.intersect(cubic, hHit) || i.intersect(cubic, vHit)) {
1194 *verbPtr = verb;
1195 *weight = 1;
1196 return counter;
1197 }
1198 } break;
Ben Wagner63fd7602017-10-09 15:45:33 -04001199 default:
caryclark64022c12016-05-27 05:13:26 -07001200 break;
1201 }
1202 }
1203 return -1;
1204 }
1205
1206 SkScalar pt_to_line(SkPoint s, SkPoint e, int x, int y) {
1207 SkScalar radius = fWidthControl.fValLo;
1208 SkVector adjOpp = e - s;
Cary Clarkdf429f32017-11-08 11:44:31 -05001209 SkScalar lenSq = SkPointPriv::LengthSqd(adjOpp);
caryclark64022c12016-05-27 05:13:26 -07001210 SkPoint rotated = {
1211 (y - s.fY) * adjOpp.fY + (x - s.fX) * adjOpp.fX,
1212 (y - s.fY) * adjOpp.fX - (x - s.fX) * adjOpp.fY,
1213 };
1214 if (rotated.fX < 0 || rotated.fX > lenSq) {
1215 return -radius;
1216 }
1217 rotated.fY /= SkScalarSqrt(lenSq);
Brian Osman788b9162020-02-07 10:36:46 -05001218 return std::max(-radius, std::min(radius, rotated.fY));
caryclark64022c12016-05-27 05:13:26 -07001219 }
1220
1221 // given a line, compute the interior and exterior gradient coverage
1222 bool coverage(SkPoint s, SkPoint e, uint8_t* distanceMap, int w, int h) {
1223 SkScalar radius = fWidthControl.fValLo;
Brian Osman788b9162020-02-07 10:36:46 -05001224 int minX = std::max(0, (int) (std::min(s.fX, e.fX) - radius));
1225 int minY = std::max(0, (int) (std::min(s.fY, e.fY) - radius));
1226 int maxX = std::min(w, (int) (std::max(s.fX, e.fX) + radius) + 1);
1227 int maxY = std::min(h, (int) (std::max(s.fY, e.fY) + radius) + 1);
caryclark64022c12016-05-27 05:13:26 -07001228 for (int y = minY; y < maxY; ++y) {
1229 for (int x = minX; x < maxX; ++x) {
1230 SkScalar ptToLineDist = pt_to_line(s, e, x, y);
1231 if (ptToLineDist > -radius && ptToLineDist < radius) {
1232 SkScalar coverage = ptToLineDist / radius;
1233 add_to_map(1 - SkScalarAbs(coverage), x, y, distanceMap, w, h);
1234 }
1235 SkVector ptToS = { x - s.fX, y - s.fY };
1236 SkScalar dist = ptToS.length();
1237 if (dist < radius) {
1238 SkScalar coverage = dist / radius;
1239 add_to_map(1 - SkScalarAbs(coverage), x, y, distanceMap, w, h);
1240 }
1241 SkVector ptToE = { x - e.fX, y - e.fY };
1242 dist = ptToE.length();
1243 if (dist < radius) {
1244 SkScalar coverage = dist / radius;
1245 add_to_map(1 - SkScalarAbs(coverage), x, y, distanceMap, w, h);
1246 }
1247 }
1248 }
1249 return true;
1250 }
1251
1252 void quad_coverage(SkPoint pts[3], uint8_t* distanceMap, int w, int h) {
1253 SkScalar dist = pts[0].Distance(pts[0], pts[2]);
1254 if (dist < gCurveDistance) {
1255 (void) coverage(pts[0], pts[2], distanceMap, w, h);
1256 return;
1257 }
1258 SkPoint split[5];
1259 SkChopQuadAt(pts, split, 0.5f);
1260 quad_coverage(&split[0], distanceMap, w, h);
1261 quad_coverage(&split[2], distanceMap, w, h);
1262 }
1263
1264 void conic_coverage(SkPoint pts[3], SkScalar weight, uint8_t* distanceMap, int w, int h) {
1265 SkScalar dist = pts[0].Distance(pts[0], pts[2]);
1266 if (dist < gCurveDistance) {
1267 (void) coverage(pts[0], pts[2], distanceMap, w, h);
1268 return;
1269 }
1270 SkConic split[2];
1271 SkConic conic;
1272 conic.set(pts, weight);
caryclark414c4292016-09-26 11:03:54 -07001273 if (conic.chopAt(0.5f, split)) {
1274 conic_coverage(split[0].fPts, split[0].fW, distanceMap, w, h);
1275 conic_coverage(split[1].fPts, split[1].fW, distanceMap, w, h);
1276 }
caryclark64022c12016-05-27 05:13:26 -07001277 }
1278
1279 void cubic_coverage(SkPoint pts[4], uint8_t* distanceMap, int w, int h) {
1280 SkScalar dist = pts[0].Distance(pts[0], pts[3]);
1281 if (dist < gCurveDistance) {
1282 (void) coverage(pts[0], pts[3], distanceMap, w, h);
1283 return;
1284 }
1285 SkPoint split[7];
1286 SkChopCubicAt(pts, split, 0.5f);
1287 cubic_coverage(&split[0], distanceMap, w, h);
1288 cubic_coverage(&split[3], distanceMap, w, h);
1289 }
1290
1291 void path_coverage(const SkPath& path, uint8_t* distanceMap, int w, int h) {
1292 memset(distanceMap, 0, sizeof(distanceMap[0]) * w * h);
1293 SkPoint pts[4];
1294 SkPath::Verb verb;
1295 SkPath::Iter iter(path, true);
1296 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1297 switch (verb) {
1298 case SkPath::kLine_Verb:
1299 (void) coverage(pts[0], pts[1], distanceMap, w, h);
1300 break;
1301 case SkPath::kQuad_Verb:
1302 quad_coverage(pts, distanceMap, w, h);
1303 break;
1304 case SkPath::kConic_Verb:
1305 conic_coverage(pts, iter.conicWeight(), distanceMap, w, h);
1306 break;
1307 case SkPath::kCubic_Verb:
1308 cubic_coverage(pts, distanceMap, w, h);
1309 break;
Ben Wagner63fd7602017-10-09 15:45:33 -04001310 default:
caryclark64022c12016-05-27 05:13:26 -07001311 break;
1312 }
1313 }
1314 }
1315
1316 static uint8_t* set_up_dist_map(const SkImageInfo& imageInfo, SkBitmap* distMap) {
1317 distMap->setInfo(imageInfo);
caryclark64022c12016-05-27 05:13:26 -07001318 SkAssertResult(distMap->tryAllocPixels());
1319 SkASSERT((int) distMap->rowBytes() == imageInfo.width());
1320 return distMap->getAddr8(0, 0);
1321 }
1322
1323 void path_stroke(int index, SkPath* inner, SkPath* outer) {
1324 #if 0
1325 SkPathStroker stroker(fPath, fWidthControl.fValLo, 0,
1326 SkPaint::kRound_Cap, SkPaint::kRound_Join, fResControl.fValLo);
1327 SkPoint pts[4], firstPt, lastPt;
1328 SkPath::Verb verb;
1329 SkPath::Iter iter(fPath, true);
1330 int counter = -1;
1331 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1332 ++counter;
1333 switch (verb) {
1334 case SkPath::kMove_Verb:
1335 firstPt = pts[0];
1336 break;
1337 case SkPath::kLine_Verb:
1338 if (counter == index) {
1339 stroker.moveTo(pts[0]);
1340 stroker.lineTo(pts[1]);
1341 goto done;
1342 }
1343 lastPt = pts[1];
1344 break;
1345 case SkPath::kQuad_Verb:
1346 if (counter == index) {
1347 stroker.moveTo(pts[0]);
1348 stroker.quadTo(pts[1], pts[2]);
1349 goto done;
1350 }
1351 lastPt = pts[2];
1352 break;
1353 case SkPath::kConic_Verb:
1354 if (counter == index) {
1355 stroker.moveTo(pts[0]);
1356 stroker.conicTo(pts[1], pts[2], iter.conicWeight());
1357 goto done;
1358 }
1359 lastPt = pts[2];
1360 break;
1361 case SkPath::kCubic_Verb:
1362 if (counter == index) {
1363 stroker.moveTo(pts[0]);
1364 stroker.cubicTo(pts[1], pts[2], pts[3]);
1365 goto done;
1366 }
1367 lastPt = pts[3];
1368 break;
1369 case SkPath::kClose_Verb:
1370 if (counter == index) {
1371 stroker.moveTo(lastPt);
1372 stroker.lineTo(firstPt);
1373 goto done;
1374 }
1375 break;
1376 case SkPath::kDone_Verb:
1377 break;
1378 default:
1379 SkASSERT(0);
1380 }
1381 }
1382 done:
1383 *inner = stroker.fInner;
1384 *outer = stroker.fOuter;
1385#endif
1386 }
1387
1388 void draw_stroke(SkCanvas* canvas, int active) {
1389 SkPath inner, outer;
1390 path_stroke(active, &inner, &outer);
1391 canvas->drawPath(inner, fSkeletonPaint);
1392 canvas->drawPath(outer, fSkeletonPaint);
1393 }
1394
1395 void gather_strokes() {
1396 fStrokes.reset();
1397 for (int index = 0; index < fPath.countVerbs(); ++index) {
1398 Stroke& inner = fStrokes.push_back();
1399 inner.reset();
1400 inner.fInner = true;
1401 Stroke& outer = fStrokes.push_back();
1402 outer.reset();
1403 outer.fInner = false;
1404 path_stroke(index, &inner.fPath, &outer.fPath);
1405 }
1406 }
1407
1408 void trim_strokes() {
1409 // eliminate self-itersecting loops
1410 // trim outside edges
1411 gather_strokes();
1412 for (int index = 0; index < fStrokes.count(); ++index) {
1413 SkPath& outPath = fStrokes[index].fPath;
1414 for (int inner = 0; inner < fStrokes.count(); ++inner) {
1415 if (index == inner) {
1416 continue;
1417 }
1418 SkPath& inPath = fStrokes[inner].fPath;
1419 if (!outPath.getBounds().intersects(inPath.getBounds())) {
1420 continue;
1421 }
Ben Wagner63fd7602017-10-09 15:45:33 -04001422
caryclark64022c12016-05-27 05:13:26 -07001423 }
1424 }
1425 }
1426
1427 void onDrawContent(SkCanvas* canvas) override {
1428#if 0
1429 SkDEBUGCODE(SkDebugStrokeGlobals debugGlobals);
1430 SkOpAA aaResult(fPath, fWidthControl.fValLo, fResControl.fValLo
1431 SkDEBUGPARAMS(&debugGlobals));
1432#endif
1433 SkPath strokePath;
1434// aaResult.simplify(&strokePath);
1435 canvas->drawPath(strokePath, fSkeletonPaint);
1436 SkRect bounds = fPath.getBounds();
1437 SkScalar radius = fWidthControl.fValLo;
1438 int w = (int) (bounds.fRight + radius + 1);
1439 int h = (int) (bounds.fBottom + radius + 1);
1440 SkImageInfo imageInfo = SkImageInfo::MakeA8(w, h);
1441 SkBitmap distMap;
1442 uint8_t* distanceMap = set_up_dist_map(imageInfo, &distMap);
1443 path_coverage(fPath, distanceMap, w, h);
1444 if (fFillButton.enabled()) {
1445 canvas->drawPath(fPath, fCoveragePaint);
1446 }
1447 if (fFilterButton.fState == 2
1448 && (0 < fFilterControl.fValLo || fFilterControl.fValHi < 255)) {
1449 SkBitmap filteredMap;
1450 uint8_t* filtered = set_up_dist_map(imageInfo, &filteredMap);
1451 filter_coverage(distanceMap, sizeof(uint8_t) * w * h, (uint8_t) fFilterControl.fValLo,
1452 (uint8_t) fFilterControl.fValHi, filtered);
1453 canvas->drawBitmap(filteredMap, 0, 0, &fCoveragePaint);
1454 } else if (fFilterButton.enabled()) {
1455 canvas->drawBitmap(distMap, 0, 0, &fCoveragePaint);
1456 }
1457 if (fSkeletonButton.enabled()) {
1458 canvas->drawPath(fPath, fActiveVerb >= 0 ? fLightSkeletonPaint : fSkeletonPaint);
1459 }
1460 if (fActiveVerb >= 0) {
1461 draw_segment(canvas);
1462 }
1463 if (fBisectButton.enabled() || fJoinButton.enabled()) {
1464 draw_bisects(canvas, fActiveVerb >= 0);
1465 }
1466 if (fInOutButton.enabled()) {
1467 if (fActiveVerb >= 0) {
1468 draw_stroke(canvas, fActiveVerb);
1469 } else {
1470 for (int index = 0; index < fPath.countVerbs(); ++index) {
1471 draw_stroke(canvas, index);
1472 }
1473 }
1474 }
1475 if (fHideAll) {
1476 return;
1477 }
1478 for (int index = 0; index < kControlCount; ++index) {
1479 kControlList[index].fControl->draw(canvas, fControlPaints);
1480 }
1481 for (int index = 0; index < kButtonCount; ++index) {
1482 kButtonList[index].fButton->draw(canvas, fButtonPaints);
1483 }
1484 if (fShowLegend) {
1485 draw_legend(canvas);
1486 }
1487
1488#if 0
1489 SkPaint paint;
1490 paint.setARGB(255, 34, 31, 31);
1491 paint.setAntiAlias(true);
1492
1493 SkPath path;
1494 path.moveTo(18,439);
1495 path.lineTo(414,439);
1496 path.lineTo(414,702);
1497 path.lineTo(18,702);
1498 path.lineTo(18,439);
1499
1500 path.moveTo(19,701);
1501 path.lineTo(413,701);
1502 path.lineTo(413,440);
1503 path.lineTo(19,440);
1504 path.lineTo(19,701);
1505 path.close();
1506 canvas->drawPath(path, paint);
1507
1508 canvas->scale(1.0f, -1.0f);
1509 canvas->translate(0.0f, -800.0f);
1510 canvas->drawPath(path, paint);
1511#endif
1512
1513 }
1514
1515 int hittest_pt(SkPoint pt) {
1516 for (int index = 0; index < fPath.countPoints(); ++index) {
1517 if (SkPoint::Distance(fPath.getPoint(index), pt) <= kHitToleranace * 2) {
1518 return index;
1519 }
1520 }
1521 return -1;
1522 }
1523
Hal Canaryb1f411a2019-08-29 10:39:22 -04001524 virtual Sample::Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey modi) override {
caryclark64022c12016-05-27 05:13:26 -07001525 SkPoint pt = {x, y};
1526 int ptHit = hittest_pt(pt);
1527 if (ptHit >= 0) {
Hal Canaryfcf63592019-07-12 11:32:43 -04001528 return new MyClick(MyClick::kPtType, ptHit);
caryclark64022c12016-05-27 05:13:26 -07001529 }
1530 SkPath::Verb verb;
1531 SkScalar weight;
1532 int verbHit = hittest_verb(pt, &verb, &weight);
1533 if (verbHit >= 0) {
Hal Canaryfcf63592019-07-12 11:32:43 -04001534 return new MyClick(MyClick::kVerbType, verbHit, verb, weight);
caryclark64022c12016-05-27 05:13:26 -07001535 }
1536 if (!fHideAll) {
1537 const SkRect& rectPt = SkRect::MakeXYWH(x, y, 1, 1);
1538 for (int index = 0; index < kControlCount; ++index) {
1539 if (kControlList[index].fControl->contains(rectPt)) {
Hal Canaryfcf63592019-07-12 11:32:43 -04001540 return new MyClick(MyClick::kControlType,
caryclark64022c12016-05-27 05:13:26 -07001541 kControlList[index].fControlType);
1542 }
1543 }
1544 for (int index = 0; index < kButtonCount; ++index) {
1545 if (kButtonList[index].fButton->contains(rectPt)) {
Hal Canaryfcf63592019-07-12 11:32:43 -04001546 return new MyClick(MyClick::kControlType, kButtonList[index].fButtonType);
caryclark64022c12016-05-27 05:13:26 -07001547 }
1548 }
1549 }
1550 fLineButton.fVisible = fQuadButton.fVisible = fConicButton.fVisible
1551 = fCubicButton.fVisible = fWeightControl.fVisible = fAddButton.fVisible
1552 = fDeleteButton.fVisible = false;
1553 fActiveVerb = -1;
1554 fActivePt = -1;
1555 if (fHandlePathMove) {
Hal Canaryfcf63592019-07-12 11:32:43 -04001556 return new MyClick(MyClick::kPathType, MyClick::kPathMove);
caryclark64022c12016-05-27 05:13:26 -07001557 }
Hal Canaryfcf63592019-07-12 11:32:43 -04001558 return nullptr;
caryclark64022c12016-05-27 05:13:26 -07001559 }
1560
1561 static SkScalar MapScreenYtoValue(int y, const UniControl& control) {
Brian Osman788b9162020-02-07 10:36:46 -05001562 return std::min(1.f, std::max(0.f,
caryclark64022c12016-05-27 05:13:26 -07001563 SkIntToScalar(y) - control.fBounds.fTop) / control.fBounds.height())
1564 * (control.fMax - control.fMin) + control.fMin;
1565 }
1566
1567 bool onClick(Click* click) override {
1568 MyClick* myClick = (MyClick*) click;
1569 switch (myClick->fType) {
1570 case MyClick::kPtType: {
1571 savePath(click->fState);
1572 fActivePt = myClick->ptHit();
1573 SkPoint pt = fPath.getPoint((int) myClick->fControl);
Hal Canaryfcf63592019-07-12 11:32:43 -04001574 pt.offset(SkIntToScalar(click->fCurr.fX - click->fPrev.fX),
1575 SkIntToScalar(click->fCurr.fY - click->fPrev.fY));
Chris Dalton8d3eb242020-05-04 10:43:33 -06001576 SkPathPriv::UpdatePathPoint(&fPath, fActivePt, pt);
caryclark64022c12016-05-27 05:13:26 -07001577 validatePath();
caryclark64022c12016-05-27 05:13:26 -07001578 return true;
1579 }
1580 case MyClick::kPathType:
1581 savePath(click->fState);
Hal Canaryfcf63592019-07-12 11:32:43 -04001582 fPath.offset(SkIntToScalar(click->fCurr.fX - click->fPrev.fX),
1583 SkIntToScalar(click->fCurr.fY - click->fPrev.fY));
caryclark64022c12016-05-27 05:13:26 -07001584 validatePath();
caryclark64022c12016-05-27 05:13:26 -07001585 return true;
1586 case MyClick::kVerbType: {
1587 fActiveVerb = myClick->verbHit();
1588 fLineButton.fVisible = fQuadButton.fVisible = fConicButton.fVisible
1589 = fCubicButton.fVisible = fAddButton.fVisible = fDeleteButton.fVisible
1590 = true;
1591 fLineButton.setEnabled(myClick->fVerb == SkPath::kLine_Verb);
1592 fQuadButton.setEnabled(myClick->fVerb == SkPath::kQuad_Verb);
1593 fConicButton.setEnabled(myClick->fVerb == SkPath::kConic_Verb);
1594 fCubicButton.setEnabled(myClick->fVerb == SkPath::kCubic_Verb);
1595 fWeightControl.fValLo = myClick->fWeight;
1596 fWeightControl.fVisible = myClick->fVerb == SkPath::kConic_Verb;
1597 } break;
1598 case MyClick::kControlType: {
Hal Canaryb1f411a2019-08-29 10:39:22 -04001599 if (click->fState != skui::InputState::kDown && myClick->isButton()) {
caryclark64022c12016-05-27 05:13:26 -07001600 return true;
1601 }
1602 switch (myClick->fControl) {
1603 case MyClick::kFilterControl: {
Hal Canaryfcf63592019-07-12 11:32:43 -04001604 SkScalar val = MapScreenYtoValue(click->fCurr.fY, fFilterControl);
caryclark64022c12016-05-27 05:13:26 -07001605 if (val - fFilterControl.fValLo < fFilterControl.fValHi - val) {
Brian Osman788b9162020-02-07 10:36:46 -05001606 fFilterControl.fValLo = std::max(0.f, val);
caryclark64022c12016-05-27 05:13:26 -07001607 } else {
Brian Osman788b9162020-02-07 10:36:46 -05001608 fFilterControl.fValHi = std::min(255.f, val);
caryclark64022c12016-05-27 05:13:26 -07001609 }
1610 } break;
1611 case MyClick::kResControl:
Hal Canaryfcf63592019-07-12 11:32:43 -04001612 fResControl.fValLo = MapScreenYtoValue(click->fCurr.fY, fResControl);
caryclark64022c12016-05-27 05:13:26 -07001613 break;
1614 case MyClick::kWeightControl: {
1615 savePath(click->fState);
Hal Canaryfcf63592019-07-12 11:32:43 -04001616 SkScalar w = MapScreenYtoValue(click->fCurr.fY, fWeightControl);
caryclark64022c12016-05-27 05:13:26 -07001617 set_path_weight(fActiveVerb, w, &fPath);
1618 validatePath();
1619 fWeightControl.fValLo = w;
1620 } break;
1621 case MyClick::kWidthControl:
Hal Canaryfcf63592019-07-12 11:32:43 -04001622 fWidthControl.fValLo = MapScreenYtoValue(click->fCurr.fY, fWidthControl);
caryclark64022c12016-05-27 05:13:26 -07001623 break;
1624 case MyClick::kLineButton:
1625 savePath(click->fState);
1626 enable_verb_button(myClick->fControl);
1627 fWeightControl.fVisible = false;
1628 set_path_verb(fActiveVerb, SkPath::kLine_Verb, &fPath, 1);
1629 validatePath();
1630 break;
1631 case MyClick::kQuadButton:
1632 savePath(click->fState);
1633 enable_verb_button(myClick->fControl);
1634 fWeightControl.fVisible = false;
1635 set_path_verb(fActiveVerb, SkPath::kQuad_Verb, &fPath, 1);
1636 validatePath();
1637 break;
1638 case MyClick::kConicButton: {
1639 savePath(click->fState);
1640 enable_verb_button(myClick->fControl);
1641 fWeightControl.fVisible = true;
1642 const SkScalar defaultConicWeight = 1.f / SkScalarSqrt(2);
1643 set_path_verb(fActiveVerb, SkPath::kConic_Verb, &fPath, defaultConicWeight);
1644 validatePath();
1645 fWeightControl.fValLo = get_path_weight(fActiveVerb, fPath);
1646 } break;
1647 case MyClick::kCubicButton:
1648 savePath(click->fState);
1649 enable_verb_button(myClick->fControl);
1650 fWeightControl.fVisible = false;
1651 set_path_verb(fActiveVerb, SkPath::kCubic_Verb, &fPath, 1);
1652 validatePath();
1653 break;
1654 case MyClick::kAddButton:
1655 savePath(click->fState);
1656 add_path_segment(fActiveVerb, &fPath);
1657 validatePath();
1658 if (fWeightControl.fVisible) {
1659 fWeightControl.fValLo = get_path_weight(fActiveVerb, fPath);
1660 }
1661 break;
1662 case MyClick::kDeleteButton:
1663 savePath(click->fState);
1664 delete_path_segment(fActiveVerb, &fPath);
1665 validatePath();
1666 break;
1667 case MyClick::kFillButton:
1668 fFillButton.toggle();
1669 break;
1670 case MyClick::kSkeletonButton:
1671 fSkeletonButton.toggle();
1672 break;
1673 case MyClick::kFilterButton:
1674 fFilterButton.toggle();
1675 fFilterControl.fVisible = fFilterButton.fState == 2;
1676 break;
1677 case MyClick::kBisectButton:
1678 fBisectButton.toggle();
1679 break;
1680 case MyClick::kJoinButton:
1681 fJoinButton.toggle();
1682 break;
1683 case MyClick::kInOutButton:
1684 fInOutButton.toggle();
1685 break;
1686 default:
1687 SkASSERT(0);
1688 break;
1689 }
1690 } break;
1691 default:
1692 SkASSERT(0);
1693 break;
1694 }
1695 setControlButtonsPos();
caryclark64022c12016-05-27 05:13:26 -07001696 return true;
1697 }
1698
1699private:
Ben Wagnerb2c4ea62018-08-08 11:36:17 -04001700 typedef Sample INHERITED;
caryclark64022c12016-05-27 05:13:26 -07001701};
1702
1703static struct KeyCommand {
1704 char fKey;
1705 char fAlternate;
1706 const char* fDescriptionL;
1707 const char* fDescriptionR;
1708 bool (AAGeometryView::*fFunction)();
1709} kKeyCommandList[] = {
Ben Wagner63fd7602017-10-09 15:45:33 -04001710 { ' ', 0, "space", "center path", &AAGeometryView::scaleToFit },
1711 { '-', 0, "-", "zoom out", &AAGeometryView::scaleDown },
1712 { '+', '=', "+/=", "zoom in", &AAGeometryView::scaleUp },
Chris Dalton08d1a252017-10-20 11:46:47 -06001713 { 'D', 0, "D", "dump to console", &AAGeometryView::pathDump },
1714 { 'H', 0, "H", "hide controls", &AAGeometryView::hideAll },
1715 { 'R', 0, "R", "reset path", &AAGeometryView::constructPath },
1716 { 'Z', 0, "Z", "undo", &AAGeometryView::undo },
caryclark64022c12016-05-27 05:13:26 -07001717 { '?', 0, "?", "show legend", &AAGeometryView::showLegend },
1718};
1719
1720const int kKeyCommandCount = (int) SK_ARRAY_COUNT(kKeyCommandList);
1721
1722void AAGeometryView::draw_legend(SkCanvas* canvas) {
1723 SkScalar bottomOffset = this->height() - 10;
1724 for (int index = kKeyCommandCount - 1; index >= 0; --index) {
1725 bottomOffset -= 15;
Mike Reedb579f072019-01-03 15:45:53 -05001726 SkTextUtils::DrawString(canvas, kKeyCommandList[index].fDescriptionL, this->width() - 160, bottomOffset,
1727 fLegendLeftFont, SkPaint());
Mike Reeda697df92018-10-26 07:28:30 -04001728 SkTextUtils::DrawString(canvas, kKeyCommandList[index].fDescriptionR,
Cary Clark2a475ea2017-04-28 15:35:12 -04001729 this->width() - 20, bottomOffset,
Mike Reedb579f072019-01-03 15:45:53 -05001730 fLegendRightFont, SkPaint(), SkTextUtils::kRight_Align);
caryclark64022c12016-05-27 05:13:26 -07001731 }
1732}
1733
Hal Canary6cc65e12019-07-03 15:53:04 -04001734bool AAGeometryView::onChar(SkUnichar uni) {
caryclark64022c12016-05-27 05:13:26 -07001735 for (int index = 0; index < kButtonCount; ++index) {
1736 Button* button = kButtonList[index].fButton;
1737 if (button->fVisible && uni == button->fLabel) {
Hal Canaryfcf63592019-07-12 11:32:43 -04001738 MyClick click(MyClick::kControlType, kButtonList[index].fButtonType);
Hal Canaryb1f411a2019-08-29 10:39:22 -04001739 click.fState = skui::InputState::kDown;
caryclark64022c12016-05-27 05:13:26 -07001740 (void) this->onClick(&click);
1741 return true;
1742 }
1743 }
1744 for (int index = 0; index < kKeyCommandCount; ++index) {
1745 KeyCommand& keyCommand = kKeyCommandList[index];
1746 if (uni == keyCommand.fKey || uni == keyCommand.fAlternate) {
1747 return (this->*keyCommand.fFunction)();
1748 }
1749 }
1750 if (('A' <= uni && uni <= 'Z') || ('a' <= uni && uni <= 'z')) {
1751 for (int index = 0; index < kButtonCount; ++index) {
1752 Button* button = kButtonList[index].fButton;
1753 if (button->fVisible && (uni & ~0x20) == (button->fLabel & ~0x20)) {
Hal Canaryfcf63592019-07-12 11:32:43 -04001754 MyClick click(MyClick::kControlType, kButtonList[index].fButtonType);
Hal Canaryb1f411a2019-08-29 10:39:22 -04001755 click.fState = skui::InputState::kDown;
caryclark64022c12016-05-27 05:13:26 -07001756 (void) this->onClick(&click);
1757 return true;
1758 }
1759 }
1760 }
Hal Canary6cc65e12019-07-03 15:53:04 -04001761 return false;
caryclark64022c12016-05-27 05:13:26 -07001762}
Ben Wagner63fd7602017-10-09 15:45:33 -04001763
caryclark64022c12016-05-27 05:13:26 -07001764DEF_SAMPLE( return new AAGeometryView; )