blob: b2aa97daf9f00e2896b1f4bd18fbe1b77b3b9e2a [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"
Hal Canary8918d532019-07-12 10:04:14 -040015#include "src/core/SkPointPriv.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050016#include "src/pathops/SkIntersections.h"
17#include "src/pathops/SkOpEdgeBuilder.h"
caryclark64022c12016-05-27 05:13:26 -070018
19#if 0
20void SkStrokeSegment::dump() const {
21 SkDebugf("{{{%1.9g,%1.9g}, {%1.9g,%1.9g}", fPts[0].fX, fPts[0].fY, fPts[1].fX, fPts[1].fY);
22 if (SkPath::kQuad_Verb == fVerb) {
23 SkDebugf(", {%1.9g,%1.9g}", fPts[2].fX, fPts[2].fY);
24 }
25 SkDebugf("}}");
26#ifdef SK_DEBUG
27 SkDebugf(" id=%d", fDebugID);
28#endif
29 SkDebugf("\n");
30}
31
32void SkStrokeSegment::dumpAll() const {
33 const SkStrokeSegment* segment = this;
34 while (segment) {
35 segment->dump();
36 segment = segment->fNext;
37 }
38}
39
40void SkStrokeTriple::dump() const {
41 SkDebugf("{{{%1.9g,%1.9g}, {%1.9g,%1.9g}", fPts[0].fX, fPts[0].fY, fPts[1].fX, fPts[1].fY);
42 if (SkPath::kQuad_Verb <= fVerb) {
43 SkDebugf(", {%1.9g,%1.9g}", fPts[2].fX, fPts[2].fY);
44 }
45 if (SkPath::kCubic_Verb == fVerb) {
46 SkDebugf(", {%1.9g,%1.9g}", fPts[3].fX, fPts[3].fY);
47 } else if (SkPath::kConic_Verb == fVerb) {
48 SkDebugf(", %1.9g", weight());
49 }
50 SkDebugf("}}");
51#ifdef SK_DEBUG
52 SkDebugf(" triple id=%d", fDebugID);
53#endif
54 SkDebugf("\ninner:\n");
55 fInner->dumpAll();
56 SkDebugf("outer:\n");
57 fOuter->dumpAll();
58 SkDebugf("join:\n");
59 fJoin->dumpAll();
60}
61
62void SkStrokeTriple::dumpAll() const {
63 const SkStrokeTriple* triple = this;
64 while (triple) {
65 triple->dump();
66 triple = triple->fNext;
67 }
68}
69
70void SkStrokeContour::dump() const {
71#ifdef SK_DEBUG
72 SkDebugf("id=%d ", fDebugID);
73#endif
74 SkDebugf("head:\n");
75 fHead->dumpAll();
76 SkDebugf("head cap:\n");
77 fHeadCap->dumpAll();
78 SkDebugf("tail cap:\n");
79 fTailCap->dumpAll();
80}
81
82void SkStrokeContour::dumpAll() const {
83 const SkStrokeContour* contour = this;
84 while (contour) {
85 contour->dump();
86 contour = contour->fNext;
87 }
88}
89#endif
90
91SkScalar gCurveDistance = 10;
92
93#if 0 // unused
94static SkPath::Verb get_path_verb(int index, const SkPath& path) {
95 if (index < 0) {
96 return SkPath::kMove_Verb;
97 }
98 SkPoint pts[4];
99 SkPath::Verb verb;
100 SkPath::Iter iter(path, true);
101 int counter = -1;
102 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
103 if (++counter < index) {
104 continue;
105 }
106 return verb;
107 }
108 SkASSERT(0);
109 return SkPath::kMove_Verb;
110}
111#endif
112
113static SkScalar get_path_weight(int index, const SkPath& path) {
114 SkPoint pts[4];
115 SkPath::Verb verb;
116 SkPath::Iter iter(path, true);
117 int counter = -1;
118 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
119 if (++counter < index) {
120 continue;
121 }
122 return verb == SkPath::kConic_Verb ? iter.conicWeight() : 1;
123 }
124 SkASSERT(0);
125 return 0;
126}
127
128static void set_path_pt(int index, const SkPoint& pt, SkPath* path) {
129 SkPath result;
130 SkPoint pts[4];
131 SkPath::Verb verb;
132 SkPath::RawIter iter(*path);
133 int startIndex = 0;
134 int endIndex = 0;
135 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
136 switch (verb) {
137 case SkPath::kMove_Verb:
138 endIndex += 1;
139 break;
140 case SkPath::kLine_Verb:
141 endIndex += 1;
142 break;
143 case SkPath::kQuad_Verb:
144 case SkPath::kConic_Verb:
145 endIndex += 2;
146 break;
147 case SkPath::kCubic_Verb:
148 endIndex += 3;
149 break;
150 case SkPath::kClose_Verb:
151 break;
152 case SkPath::kDone_Verb:
153 break;
154 default:
155 SkASSERT(0);
156 }
157 if (startIndex <= index && index < endIndex) {
158 pts[index - startIndex] = pt;
159 index = -1;
160 }
161 switch (verb) {
162 case SkPath::kMove_Verb:
163 result.moveTo(pts[0]);
164 break;
165 case SkPath::kLine_Verb:
166 result.lineTo(pts[1]);
167 startIndex += 1;
168 break;
169 case SkPath::kQuad_Verb:
170 result.quadTo(pts[1], pts[2]);
171 startIndex += 2;
172 break;
173 case SkPath::kConic_Verb:
174 result.conicTo(pts[1], pts[2], iter.conicWeight());
175 startIndex += 2;
176 break;
177 case SkPath::kCubic_Verb:
178 result.cubicTo(pts[1], pts[2], pts[3]);
179 startIndex += 3;
180 break;
181 case SkPath::kClose_Verb:
182 result.close();
183 startIndex += 1;
184 break;
185 case SkPath::kDone_Verb:
186 break;
187 default:
188 SkASSERT(0);
189 }
190 }
191#if 0
192 SkDebugf("\n\noriginal\n");
193 path->dump();
194 SkDebugf("\nedited\n");
195 result.dump();
196#endif
197 *path = result;
198}
199
200static void add_path_segment(int index, SkPath* path) {
201 SkPath result;
202 SkPoint pts[4];
203 SkPoint firstPt = { 0, 0 }; // init to avoid warning
204 SkPoint lastPt = { 0, 0 }; // init to avoid warning
205 SkPath::Verb verb;
206 SkPath::RawIter iter(*path);
207 int counter = -1;
208 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
209 SkScalar weight SK_INIT_TO_AVOID_WARNING;
210 if (++counter == index) {
211 switch (verb) {
212 case SkPath::kLine_Verb:
213 result.lineTo((pts[0].fX + pts[1].fX) / 2, (pts[0].fY + pts[1].fY) / 2);
214 break;
215 case SkPath::kQuad_Verb: {
216 SkPoint chop[5];
217 SkChopQuadAtHalf(pts, chop);
218 result.quadTo(chop[1], chop[2]);
219 pts[1] = chop[3];
220 } break;
221 case SkPath::kConic_Verb: {
222 SkConic chop[2];
223 SkConic conic;
224 conic.set(pts, iter.conicWeight());
caryclark414c4292016-09-26 11:03:54 -0700225 if (!conic.chopAt(0.5f, chop)) {
226 return;
227 }
caryclark64022c12016-05-27 05:13:26 -0700228 result.conicTo(chop[0].fPts[1], chop[0].fPts[2], chop[0].fW);
229 pts[1] = chop[1].fPts[1];
230 weight = chop[1].fW;
231 } break;
232 case SkPath::kCubic_Verb: {
233 SkPoint chop[7];
234 SkChopCubicAtHalf(pts, chop);
235 result.cubicTo(chop[1], chop[2], chop[3]);
236 pts[1] = chop[4];
237 pts[2] = chop[5];
238 } break;
239 case SkPath::kClose_Verb: {
240 result.lineTo((lastPt.fX + firstPt.fX) / 2, (lastPt.fY + firstPt.fY) / 2);
241 } break;
242 default:
243 SkASSERT(0);
244 }
245 } else if (verb == SkPath::kConic_Verb) {
246 weight = iter.conicWeight();
247 }
248 switch (verb) {
249 case SkPath::kMove_Verb:
250 result.moveTo(firstPt = pts[0]);
251 break;
252 case SkPath::kLine_Verb:
253 result.lineTo(lastPt = pts[1]);
254 break;
255 case SkPath::kQuad_Verb:
256 result.quadTo(pts[1], lastPt = pts[2]);
257 break;
258 case SkPath::kConic_Verb:
259 result.conicTo(pts[1], lastPt = pts[2], weight);
260 break;
261 case SkPath::kCubic_Verb:
262 result.cubicTo(pts[1], pts[2], lastPt = pts[3]);
263 break;
264 case SkPath::kClose_Verb:
265 result.close();
266 break;
267 case SkPath::kDone_Verb:
268 break;
269 default:
270 SkASSERT(0);
271 }
272 }
273 *path = result;
274}
275
276static void delete_path_segment(int index, SkPath* path) {
277 SkPath result;
278 SkPoint pts[4];
279 SkPath::Verb verb;
280 SkPath::RawIter iter(*path);
281 int counter = -1;
282 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
283 if (++counter == index) {
284 continue;
285 }
286 switch (verb) {
287 case SkPath::kMove_Verb:
288 result.moveTo(pts[0]);
289 break;
290 case SkPath::kLine_Verb:
291 result.lineTo(pts[1]);
292 break;
293 case SkPath::kQuad_Verb:
294 result.quadTo(pts[1], pts[2]);
295 break;
296 case SkPath::kConic_Verb:
297 result.conicTo(pts[1], pts[2], iter.conicWeight());
298 break;
299 case SkPath::kCubic_Verb:
300 result.cubicTo(pts[1], pts[2], pts[3]);
301 break;
302 case SkPath::kClose_Verb:
303 result.close();
304 break;
305 case SkPath::kDone_Verb:
306 break;
307 default:
308 SkASSERT(0);
309 }
310 }
311 *path = result;
312}
313
314static void set_path_weight(int index, SkScalar w, SkPath* path) {
315 SkPath result;
316 SkPoint pts[4];
317 SkPath::Verb verb;
318 SkPath::Iter iter(*path, true);
319 int counter = -1;
320 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
321 ++counter;
322 switch (verb) {
323 case SkPath::kMove_Verb:
324 result.moveTo(pts[0]);
325 break;
326 case SkPath::kLine_Verb:
327 result.lineTo(pts[1]);
328 break;
329 case SkPath::kQuad_Verb:
330 result.quadTo(pts[1], pts[2]);
331 break;
332 case SkPath::kConic_Verb:
333 result.conicTo(pts[1], pts[2], counter == index ? w : iter.conicWeight());
334 break;
335 case SkPath::kCubic_Verb:
336 result.cubicTo(pts[1], pts[2], pts[3]);
337 break;
338 case SkPath::kClose_Verb:
339 result.close();
340 break;
341 case SkPath::kDone_Verb:
342 break;
343 default:
344 SkASSERT(0);
345 }
346 }
347 *path = result;
348}
349
350static void set_path_verb(int index, SkPath::Verb v, SkPath* path, SkScalar w) {
351 SkASSERT(SkPath::kLine_Verb <= v && v <= SkPath::kCubic_Verb);
352 SkPath result;
353 SkPoint pts[4];
354 SkPath::Verb verb;
355 SkPath::Iter iter(*path, true);
356 int counter = -1;
357 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
358 SkScalar weight = verb == SkPath::kConic_Verb ? iter.conicWeight() : 1;
359 if (++counter == index && v != verb) {
360 SkASSERT(SkPath::kLine_Verb <= verb && verb <= SkPath::kCubic_Verb);
361 switch (verb) {
362 case SkPath::kLine_Verb:
363 switch (v) {
364 case SkPath::kConic_Verb:
365 weight = w;
366 case SkPath::kQuad_Verb:
367 pts[2] = pts[1];
368 pts[1].fX = (pts[0].fX + pts[2].fX) / 2;
369 pts[1].fY = (pts[0].fY + pts[2].fY) / 2;
370 break;
371 case SkPath::kCubic_Verb:
372 pts[3] = pts[1];
373 pts[1].fX = (pts[0].fX * 2 + pts[3].fX) / 3;
374 pts[1].fY = (pts[0].fY * 2 + pts[3].fY) / 3;
375 pts[2].fX = (pts[0].fX + pts[3].fX * 2) / 3;
376 pts[2].fY = (pts[0].fY + pts[3].fY * 2) / 3;
377 break;
378 default:
379 SkASSERT(0);
380 break;
381 }
382 break;
383 case SkPath::kQuad_Verb:
384 case SkPath::kConic_Verb:
385 switch (v) {
386 case SkPath::kLine_Verb:
387 pts[1] = pts[2];
388 break;
389 case SkPath::kConic_Verb:
390 weight = w;
391 case SkPath::kQuad_Verb:
392 break;
393 case SkPath::kCubic_Verb: {
394 SkDQuad dQuad;
395 dQuad.set(pts);
396 SkDCubic dCubic = dQuad.debugToCubic();
397 pts[3] = pts[2];
398 pts[1] = dCubic[1].asSkPoint();
399 pts[2] = dCubic[2].asSkPoint();
400 } break;
401 default:
402 SkASSERT(0);
403 break;
404 }
405 break;
406 case SkPath::kCubic_Verb:
407 switch (v) {
408 case SkPath::kLine_Verb:
409 pts[1] = pts[3];
410 break;
411 case SkPath::kConic_Verb:
412 weight = w;
413 case SkPath::kQuad_Verb: {
414 SkDCubic dCubic;
415 dCubic.set(pts);
416 SkDQuad dQuad = dCubic.toQuad();
417 pts[1] = dQuad[1].asSkPoint();
418 pts[2] = pts[3];
419 } break;
420 default:
421 SkASSERT(0);
422 break;
423 }
424 break;
425 default:
426 SkASSERT(0);
427 break;
428 }
429 verb = v;
430 }
431 switch (verb) {
432 case SkPath::kMove_Verb:
433 result.moveTo(pts[0]);
434 break;
435 case SkPath::kLine_Verb:
436 result.lineTo(pts[1]);
437 break;
438 case SkPath::kQuad_Verb:
439 result.quadTo(pts[1], pts[2]);
440 break;
441 case SkPath::kConic_Verb:
442 result.conicTo(pts[1], pts[2], weight);
443 break;
444 case SkPath::kCubic_Verb:
445 result.cubicTo(pts[1], pts[2], pts[3]);
446 break;
447 case SkPath::kClose_Verb:
448 result.close();
449 break;
450 default:
451 SkASSERT(0);
452 break;
453 }
454 }
455 *path = result;
456}
457
458static void add_to_map(SkScalar coverage, int x, int y, uint8_t* distanceMap, int w, int h) {
459 int byteCoverage = (int) (coverage * 256);
460 if (byteCoverage < 0) {
461 byteCoverage = 0;
462 } else if (byteCoverage > 255) {
463 byteCoverage = 255;
464 }
465 SkASSERT(x < w);
466 SkASSERT(y < h);
467 distanceMap[y * w + x] = SkTMax(distanceMap[y * w + x], (uint8_t) byteCoverage);
468}
469
470static void filter_coverage(const uint8_t* map, int len, uint8_t min, uint8_t max,
471 uint8_t* filter) {
472 for (int index = 0; index < len; ++index) {
473 uint8_t in = map[index];
474 filter[index] = in < min ? 0 : max < in ? 0 : in;
475 }
476}
477
478static void construct_path(SkPath& path) {
479 path.reset();
480 path.moveTo(442, 101.5f);
481 path.quadTo(413.5f, 691, 772, 514);
482 path.lineTo(346, 721.5f);
483 path.lineTo(154, 209);
484 path.lineTo(442, 101.5f);
485 path.close();
486}
487
488struct ButtonPaints {
489 static const int kMaxStateCount = 3;
490 SkPaint fDisabled;
491 SkPaint fStates[kMaxStateCount];
Mike Reedb579f072019-01-03 15:45:53 -0500492 SkFont fLabelFont;
caryclark64022c12016-05-27 05:13:26 -0700493
494 ButtonPaints() {
495 fStates[0].setAntiAlias(true);
496 fStates[0].setStyle(SkPaint::kStroke_Style);
497 fStates[0].setColor(0xFF3F0000);
498 fStates[1] = fStates[0];
499 fStates[1].setStrokeWidth(3);
500 fStates[2] = fStates[1];
501 fStates[2].setColor(0xFFcf0000);
Mike Reedb579f072019-01-03 15:45:53 -0500502 fLabelFont.setSize(25.0f);
caryclark64022c12016-05-27 05:13:26 -0700503 }
504};
505
506struct Button {
507 SkRect fBounds;
508 int fStateCount;
509 int fState;
510 char fLabel;
511 bool fVisible;
512
513 Button(char label) {
514 fStateCount = 2;
515 fState = 0;
516 fLabel = label;
517 fVisible = false;
518 }
519
520 Button(char label, int stateCount) {
521 SkASSERT(stateCount <= ButtonPaints::kMaxStateCount);
522 fStateCount = stateCount;
523 fState = 0;
524 fLabel = label;
525 fVisible = false;
526 }
527
528 bool contains(const SkRect& rect) {
529 return fVisible && fBounds.contains(rect);
530 }
531
532 bool enabled() {
533 return SkToBool(fState);
534 }
535
536 void draw(SkCanvas* canvas, const ButtonPaints& paints) {
537 if (!fVisible) {
538 return;
539 }
540 canvas->drawRect(fBounds, paints.fStates[fState]);
Ben Wagner51e15a62019-05-07 15:38:46 -0400541 SkTextUtils::Draw(canvas, &fLabel, 1, SkTextEncoding::kUTF8, fBounds.centerX(), fBounds.fBottom - 5,
Mike Reedb579f072019-01-03 15:45:53 -0500542 paints.fLabelFont, SkPaint(), SkTextUtils::kCenter_Align);
caryclark64022c12016-05-27 05:13:26 -0700543 }
544
545 void toggle() {
546 if (++fState == fStateCount) {
547 fState = 0;
548 }
549 }
550
551 void setEnabled(bool enabled) {
552 fState = (int) enabled;
553 }
554};
555
556struct ControlPaints {
557 SkPaint fOutline;
558 SkPaint fIndicator;
559 SkPaint fFill;
560 SkPaint fLabel;
561 SkPaint fValue;
562
Mike Reed89126e42019-01-03 12:59:14 -0500563 SkFont fLabelFont;
564 SkFont fValueFont;
565
caryclark64022c12016-05-27 05:13:26 -0700566 ControlPaints() {
567 fOutline.setAntiAlias(true);
568 fOutline.setStyle(SkPaint::kStroke_Style);
569 fIndicator = fOutline;
570 fIndicator.setColor(SK_ColorRED);
571 fFill.setAntiAlias(true);
572 fFill.setColor(0x7fff0000);
573 fLabel.setAntiAlias(true);
Mike Reed89126e42019-01-03 12:59:14 -0500574 fLabelFont.setSize(13.0f);
caryclark64022c12016-05-27 05:13:26 -0700575 fValue.setAntiAlias(true);
Mike Reed89126e42019-01-03 12:59:14 -0500576 fValueFont.setSize(11.0f);
caryclark64022c12016-05-27 05:13:26 -0700577 }
578};
579
580struct UniControl {
581 SkString fName;
582 SkRect fBounds;
583 SkScalar fMin;
584 SkScalar fMax;
585 SkScalar fValLo;
586 SkScalar fYLo;
587 bool fVisible;
588
589 UniControl(const char* name, SkScalar min, SkScalar max) {
590 fName = name;
591 fValLo = fMin = min;
592 fMax = max;
593 fVisible = false;
594
595 }
596
597 virtual ~UniControl() {}
598
599 bool contains(const SkRect& rect) {
600 return fVisible && fBounds.contains(rect);
601 }
602
603 virtual void draw(SkCanvas* canvas, const ControlPaints& paints) {
604 if (!fVisible) {
605 return;
606 }
607 canvas->drawRect(fBounds, paints.fOutline);
608 fYLo = fBounds.fTop + (fValLo - fMin) * fBounds.height() / (fMax - fMin);
609 canvas->drawLine(fBounds.fLeft - 5, fYLo, fBounds.fRight + 5, fYLo, paints.fIndicator);
610 SkString label;
611 label.printf("%0.3g", fValLo);
Hal Canary89a644b2019-01-07 09:36:09 -0500612 canvas->drawString(label, fBounds.fLeft + 5, fYLo - 5, paints.fValueFont, paints.fValue);
613 canvas->drawString(fName, fBounds.fLeft, fBounds.bottom() + 11, paints.fLabelFont,
614 paints.fLabel);
caryclark64022c12016-05-27 05:13:26 -0700615 }
616};
617
618struct BiControl : public UniControl {
619 SkScalar fValHi;
620
Ben Wagner63fd7602017-10-09 15:45:33 -0400621 BiControl(const char* name, SkScalar min, SkScalar max)
caryclark64022c12016-05-27 05:13:26 -0700622 : UniControl(name, min, max)
623 , fValHi(fMax) {
624 }
Ben Wagner63fd7602017-10-09 15:45:33 -0400625
caryclark64022c12016-05-27 05:13:26 -0700626 virtual ~BiControl() {}
627
628 virtual void draw(SkCanvas* canvas, const ControlPaints& paints) {
629 UniControl::draw(canvas, paints);
630 if (!fVisible || fValHi == fValLo) {
631 return;
632 }
633 SkScalar yPos = fBounds.fTop + (fValHi - fMin) * fBounds.height() / (fMax - fMin);
634 canvas->drawLine(fBounds.fLeft - 5, yPos, fBounds.fRight + 5, yPos, paints.fIndicator);
635 SkString label;
636 label.printf("%0.3g", fValHi);
637 if (yPos < fYLo + 10) {
638 yPos = fYLo + 10;
639 }
Hal Canary4484b8f2019-01-08 14:00:08 -0500640 canvas->drawString(label, fBounds.fLeft + 5, yPos - 5, paints.fValueFont, paints.fValue);
caryclark64022c12016-05-27 05:13:26 -0700641 SkRect fill = { fBounds.fLeft, fYLo, fBounds.fRight, yPos };
642 canvas->drawRect(fill, paints.fFill);
643 }
644};
645
646
Ben Wagnerb2c4ea62018-08-08 11:36:17 -0400647class MyClick : public Sample::Click {
caryclark64022c12016-05-27 05:13:26 -0700648public:
649 enum ClickType {
650 kInvalidType = -1,
651 kPtType,
652 kVerbType,
653 kControlType,
654 kPathType,
655 } fType;
656
657 enum ControlType {
658 kInvalidControl = -1,
659 kFirstControl,
660 kFilterControl = kFirstControl,
661 kResControl,
662 kWeightControl,
663 kWidthControl,
664 kLastControl = kWidthControl,
665 kFirstButton,
666 kCubicButton = kFirstButton,
667 kConicButton,
668 kQuadButton,
669 kLineButton,
670 kLastVerbButton = kLineButton,
671 kAddButton,
672 kDeleteButton,
673 kInOutButton,
674 kFillButton,
675 kSkeletonButton,
676 kFilterButton,
677 kBisectButton,
678 kJoinButton,
679 kLastButton = kJoinButton,
680 kPathMove,
681 } fControl;
682
683 SkPath::Verb fVerb;
684 SkScalar fWeight;
685
Hal Canaryfcf63592019-07-12 11:32:43 -0400686 MyClick(ClickType type, ControlType control)
687 : fType(type)
caryclark64022c12016-05-27 05:13:26 -0700688 , fControl(control)
689 , fVerb((SkPath::Verb) -1)
690 , fWeight(1) {
691 }
692
Hal Canaryfcf63592019-07-12 11:32:43 -0400693 MyClick(ClickType type, int index)
694 : fType(type)
caryclark64022c12016-05-27 05:13:26 -0700695 , fControl((ControlType) index)
696 , fVerb((SkPath::Verb) -1)
697 , fWeight(1) {
698 }
699
Hal Canaryfcf63592019-07-12 11:32:43 -0400700 MyClick(ClickType type, int index, SkPath::Verb verb, SkScalar weight)
701 : fType(type)
caryclark64022c12016-05-27 05:13:26 -0700702 , fControl((ControlType) index)
703 , fVerb(verb)
704 , fWeight(weight) {
705 }
706
707 bool isButton() {
708 return kFirstButton <= fControl && fControl <= kLastButton;
709 }
710
711 int ptHit() const {
712 SkASSERT(fType == kPtType);
713 return (int) fControl;
714 }
715
716 int verbHit() const {
717 SkASSERT(fType == kVerbType);
718 return (int) fControl;
719 }
720};
721
722enum {
723 kControlCount = MyClick::kLastControl - MyClick::kFirstControl + 1,
724};
725
726static struct ControlPair {
727 UniControl* fControl;
728 MyClick::ControlType fControlType;
729} kControlList[kControlCount];
730
731enum {
732 kButtonCount = MyClick::kLastButton - MyClick::kFirstButton + 1,
733 kVerbCount = MyClick::kLastVerbButton - MyClick::kFirstButton + 1,
734};
735
736static struct ButtonPair {
737 Button* fButton;
738 MyClick::ControlType fButtonType;
739} kButtonList[kButtonCount];
740
741static void enable_verb_button(MyClick::ControlType type) {
742 for (int index = 0; index < kButtonCount; ++index) {
743 MyClick::ControlType testType = kButtonList[index].fButtonType;
744 if (MyClick::kFirstButton <= testType && testType <= MyClick::kLastVerbButton) {
745 Button* button = kButtonList[index].fButton;
746 button->setEnabled(testType == type);
747 }
748 }
749}
750
751struct Stroke;
752
753struct Active {
754 Active* fNext;
755 Stroke* fParent;
756 SkScalar fStart;
757 SkScalar fEnd;
758
759 void reset() {
Ben Wagnera93a14a2017-08-28 10:34:05 -0400760 fNext = nullptr;
caryclark64022c12016-05-27 05:13:26 -0700761 fStart = 0;
762 fEnd = 1;
763 }
764};
765
766struct Stroke {
767 SkPath fPath;
768 Active fActive;
769 bool fInner;
770
771 void reset() {
772 fPath.reset();
773 fActive.reset();
774 }
775};
776
777struct PathUndo {
778 SkPath fPath;
Hal Canary8918d532019-07-12 10:04:14 -0400779 std::unique_ptr<PathUndo> fNext;
caryclark64022c12016-05-27 05:13:26 -0700780};
781
Ben Wagnerb2c4ea62018-08-08 11:36:17 -0400782class AAGeometryView : public Sample {
caryclark64022c12016-05-27 05:13:26 -0700783 SkPaint fActivePaint;
784 SkPaint fComplexPaint;
785 SkPaint fCoveragePaint;
Mike Reedb579f072019-01-03 15:45:53 -0500786 SkFont fLegendLeftFont;
787 SkFont fLegendRightFont;
caryclark64022c12016-05-27 05:13:26 -0700788 SkPaint fPointPaint;
789 SkPaint fSkeletonPaint;
790 SkPaint fLightSkeletonPaint;
791 SkPath fPath;
792 ControlPaints fControlPaints;
793 UniControl fResControl;
794 UniControl fWeightControl;
795 UniControl fWidthControl;
796 BiControl fFilterControl;
797 ButtonPaints fButtonPaints;
798 Button fCubicButton;
799 Button fConicButton;
800 Button fQuadButton;
801 Button fLineButton;
802 Button fAddButton;
803 Button fDeleteButton;
804 Button fFillButton;
805 Button fSkeletonButton;
806 Button fFilterButton;
807 Button fBisectButton;
808 Button fJoinButton;
809 Button fInOutButton;
810 SkTArray<Stroke> fStrokes;
Hal Canary8918d532019-07-12 10:04:14 -0400811 std::unique_ptr<PathUndo> fUndo;
caryclark64022c12016-05-27 05:13:26 -0700812 int fActivePt;
813 int fActiveVerb;
814 bool fHandlePathMove;
815 bool fShowLegend;
816 bool fHideAll;
Chris Dalton08d1a252017-10-20 11:46:47 -0600817 const int kHitToleranace = 25;
caryclark64022c12016-05-27 05:13:26 -0700818
819public:
820
Ben Wagner63fd7602017-10-09 15:45:33 -0400821 AAGeometryView()
caryclark64022c12016-05-27 05:13:26 -0700822 : fResControl("error", 0, 10)
823 , fWeightControl("weight", 0, 5)
824 , fWidthControl("width", FLT_EPSILON, 100)
825 , fFilterControl("filter", 0, 255)
826 , fCubicButton('C')
827 , fConicButton('K')
828 , fQuadButton('Q')
829 , fLineButton('L')
830 , fAddButton('+')
831 , fDeleteButton('x')
832 , fFillButton('p')
833 , fSkeletonButton('s')
834 , fFilterButton('f', 3)
835 , fBisectButton('b')
836 , fJoinButton('j')
837 , fInOutButton('|')
caryclark64022c12016-05-27 05:13:26 -0700838 , fActivePt(-1)
839 , fActiveVerb(-1)
840 , fHandlePathMove(true)
841 , fShowLegend(false)
842 , fHideAll(false)
843 {
844 fCoveragePaint.setAntiAlias(true);
845 fCoveragePaint.setColor(SK_ColorBLUE);
846 SkPaint strokePaint;
847 strokePaint.setAntiAlias(true);
848 strokePaint.setStyle(SkPaint::kStroke_Style);
849 fPointPaint = strokePaint;
850 fPointPaint.setColor(0x99ee3300);
851 fSkeletonPaint = strokePaint;
852 fSkeletonPaint.setColor(SK_ColorRED);
853 fLightSkeletonPaint = fSkeletonPaint;
854 fLightSkeletonPaint.setColor(0xFFFF7f7f);
855 fActivePaint = strokePaint;
856 fActivePaint.setColor(0x99ee3300);
857 fActivePaint.setStrokeWidth(5);
858 fComplexPaint = fActivePaint;
859 fComplexPaint.setColor(SK_ColorBLUE);
Mike Reedb579f072019-01-03 15:45:53 -0500860 fLegendLeftFont.setSize(13);
861 fLegendRightFont = fLegendLeftFont;
caryclark64022c12016-05-27 05:13:26 -0700862 construct_path(fPath);
863 fFillButton.fVisible = fSkeletonButton.fVisible = fFilterButton.fVisible
864 = fBisectButton.fVisible = fJoinButton.fVisible = fInOutButton.fVisible = true;
865 fSkeletonButton.setEnabled(true);
866 fInOutButton.setEnabled(true);
867 fJoinButton.setEnabled(true);
868 fFilterControl.fValLo = 120;
869 fFilterControl.fValHi = 141;
870 fFilterControl.fVisible = fFilterButton.fState == 2;
871 fResControl.fValLo = 5;
872 fResControl.fVisible = true;
873 fWidthControl.fValLo = 50;
874 fWidthControl.fVisible = true;
875 init_controlList();
876 init_buttonList();
877 }
878
Hal Canary8918d532019-07-12 10:04:14 -0400879 ~AAGeometryView() override {
880 // Free linked list without deep recursion.
881 std::unique_ptr<PathUndo> undo = std::move(fUndo);
882 while (undo) {
883 undo = std::move(undo->fNext);
884 }
885 }
886
caryclark64022c12016-05-27 05:13:26 -0700887 bool constructPath() {
888 construct_path(fPath);
caryclark64022c12016-05-27 05:13:26 -0700889 return true;
890 }
891
Hal Canaryb1f411a2019-08-29 10:39:22 -0400892 void savePath(skui::InputState state) {
893 if (state != skui::InputState::kDown) {
caryclark64022c12016-05-27 05:13:26 -0700894 return;
895 }
896 if (fUndo && fUndo->fPath == fPath) {
897 return;
898 }
Hal Canary8918d532019-07-12 10:04:14 -0400899 std::unique_ptr<PathUndo> undo(new PathUndo);
caryclark64022c12016-05-27 05:13:26 -0700900 undo->fPath = fPath;
Hal Canary8918d532019-07-12 10:04:14 -0400901 undo->fNext = std::move(fUndo);
902 fUndo = std::move(undo);
caryclark64022c12016-05-27 05:13:26 -0700903 }
904
905 bool undo() {
906 if (!fUndo) {
907 return false;
908 }
Hal Canary8918d532019-07-12 10:04:14 -0400909 fPath = std::move(fUndo->fPath);
910 fUndo = std::move(fUndo->fNext);
caryclark64022c12016-05-27 05:13:26 -0700911 validatePath();
caryclark64022c12016-05-27 05:13:26 -0700912 return true;
913 }
914
Hal Canary8918d532019-07-12 10:04:14 -0400915 void validatePath() {}
caryclark64022c12016-05-27 05:13:26 -0700916
917 void set_controlList(int index, UniControl* control, MyClick::ControlType type) {
918 kControlList[index].fControl = control;
919 kControlList[index].fControlType = type;
920 }
921
922 #define SET_CONTROL(Name) set_controlList(index++, &f##Name##Control, \
923 MyClick::k##Name##Control)
924
925 bool hideAll() {
926 fHideAll ^= true;
caryclark64022c12016-05-27 05:13:26 -0700927 return true;
928 }
929
930 void init_controlList() {
931 int index = 0;
932 SET_CONTROL(Width);
933 SET_CONTROL(Res);
934 SET_CONTROL(Filter);
935 SET_CONTROL(Weight);
Brian Osman16adfa32016-10-18 14:42:44 -0400936 }
Ben Wagner63fd7602017-10-09 15:45:33 -0400937
caryclark64022c12016-05-27 05:13:26 -0700938 #undef SET_CONTROL
939
940 void set_buttonList(int index, Button* button, MyClick::ControlType type) {
941 kButtonList[index].fButton = button;
942 kButtonList[index].fButtonType = type;
943 }
944
945 #define SET_BUTTON(Name) set_buttonList(index++, &f##Name##Button, \
946 MyClick::k##Name##Button)
947
948 void init_buttonList() {
949 int index = 0;
950 SET_BUTTON(Fill);
951 SET_BUTTON(Skeleton);
952 SET_BUTTON(Filter);
953 SET_BUTTON(Bisect);
954 SET_BUTTON(Join);
955 SET_BUTTON(InOut);
956 SET_BUTTON(Cubic);
957 SET_BUTTON(Conic);
958 SET_BUTTON(Quad);
959 SET_BUTTON(Line);
960 SET_BUTTON(Add);
961 SET_BUTTON(Delete);
962 }
963
964 #undef SET_BUTTON
965
Hal Canary8a027312019-07-03 10:55:44 -0400966 SkString name() override { return SkString("AAGeometry"); }
967
Hal Canary6cc65e12019-07-03 15:53:04 -0400968 bool onChar(SkUnichar) override;
Ben Wagner63fd7602017-10-09 15:45:33 -0400969
caryclark64022c12016-05-27 05:13:26 -0700970 void onSizeChange() override {
971 setControlButtonsPos();
972 this->INHERITED::onSizeChange();
973 }
974
975 bool pathDump() {
976 fPath.dump();
977 return true;
978 }
979
980 bool scaleDown() {
981 SkMatrix matrix;
982 SkRect bounds = fPath.getBounds();
983 matrix.setScale(1.f / 1.5f, 1.f / 1.5f, bounds.centerX(), bounds.centerY());
984 fPath.transform(matrix);
985 validatePath();
caryclark64022c12016-05-27 05:13:26 -0700986 return true;
987 }
988
989 bool scaleToFit() {
990 SkMatrix matrix;
991 SkRect bounds = fPath.getBounds();
992 SkScalar scale = SkTMin(this->width() / bounds.width(), this->height() / bounds.height())
993 * 0.8f;
994 matrix.setScale(scale, scale, bounds.centerX(), bounds.centerY());
995 fPath.transform(matrix);
996 bounds = fPath.getBounds();
997 SkScalar offsetX = (this->width() - bounds.width()) / 2 - bounds.fLeft;
998 SkScalar offsetY = (this->height() - bounds.height()) / 2 - bounds.fTop;
999 fPath.offset(offsetX, offsetY);
1000 validatePath();
caryclark64022c12016-05-27 05:13:26 -07001001 return true;
1002 }
1003
1004 bool scaleUp() {
1005 SkMatrix matrix;
1006 SkRect bounds = fPath.getBounds();
1007 matrix.setScale(1.5f, 1.5f, bounds.centerX(), bounds.centerY());
1008 fPath.transform(matrix);
1009 validatePath();
caryclark64022c12016-05-27 05:13:26 -07001010 return true;
1011 }
1012
1013 void setControlButtonsPos() {
1014 SkScalar widthOffset = this->width() - 100;
1015 for (int index = 0; index < kControlCount; ++index) {
1016 if (kControlList[index].fControl->fVisible) {
1017 kControlList[index].fControl->fBounds.setXYWH(widthOffset, 30, 30, 400);
1018 widthOffset -= 50;
1019 }
1020 }
1021 SkScalar buttonOffset = 0;
1022 for (int index = 0; index < kButtonCount; ++index) {
1023 kButtonList[index].fButton->fBounds.setXYWH(this->width() - 50,
1024 buttonOffset += 50, 30, 30);
1025 }
1026 }
1027
1028 bool showLegend() {
1029 fShowLegend ^= true;
caryclark64022c12016-05-27 05:13:26 -07001030 return true;
1031 }
1032
1033 void draw_bisect(SkCanvas* canvas, const SkVector& lastVector, const SkVector& vector,
1034 const SkPoint& pt) {
1035 SkVector lastV = lastVector;
1036 SkScalar lastLen = lastVector.length();
1037 SkVector nextV = vector;
1038 SkScalar nextLen = vector.length();
1039 if (lastLen < nextLen) {
1040 lastV.setLength(nextLen);
1041 } else {
1042 nextV.setLength(lastLen);
1043 }
1044
1045 SkVector bisect = { (lastV.fX + nextV.fX) / 2, (lastV.fY + nextV.fY) / 2 };
1046 bisect.setLength(fWidthControl.fValLo * 2);
1047 if (fBisectButton.enabled()) {
Hal Canary23e474c2017-05-15 13:35:35 -04001048 canvas->drawLine(pt, pt + bisect, fSkeletonPaint);
caryclark64022c12016-05-27 05:13:26 -07001049 }
1050 lastV.setLength(fWidthControl.fValLo);
1051 if (fBisectButton.enabled()) {
Hal Canary23e474c2017-05-15 13:35:35 -04001052 canvas->drawLine(pt, {pt.fX - lastV.fY, pt.fY + lastV.fX}, fSkeletonPaint);
caryclark64022c12016-05-27 05:13:26 -07001053 }
1054 nextV.setLength(fWidthControl.fValLo);
1055 if (fBisectButton.enabled()) {
Hal Canary23e474c2017-05-15 13:35:35 -04001056 canvas->drawLine(pt, {pt.fX + nextV.fY, pt.fY - nextV.fX}, fSkeletonPaint);
caryclark64022c12016-05-27 05:13:26 -07001057 }
1058 if (fJoinButton.enabled()) {
1059 SkScalar r = fWidthControl.fValLo;
1060 SkRect oval = { pt.fX - r, pt.fY - r, pt.fX + r, pt.fY + r};
1061 SkScalar startAngle = SkScalarATan2(lastV.fX, -lastV.fY) * 180.f / SK_ScalarPI;
1062 SkScalar endAngle = SkScalarATan2(-nextV.fX, nextV.fY) * 180.f / SK_ScalarPI;
1063 if (endAngle > startAngle) {
1064 canvas->drawArc(oval, startAngle, endAngle - startAngle, false, fSkeletonPaint);
1065 } else {
1066 canvas->drawArc(oval, startAngle, 360 - (startAngle - endAngle), false,
1067 fSkeletonPaint);
1068 }
1069 }
1070 }
1071
1072 void draw_bisects(SkCanvas* canvas, bool activeOnly) {
1073 SkVector firstVector, lastVector, nextLast, vector;
1074 SkPoint pts[4];
1075 SkPoint firstPt = { 0, 0 }; // init to avoid warning;
1076 SkPath::Verb verb;
1077 SkPath::Iter iter(fPath, true);
1078 bool foundFirst = false;
1079 int counter = -1;
1080 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1081 ++counter;
1082 if (activeOnly && counter != fActiveVerb && counter - 1 != fActiveVerb
1083 && counter + 1 != fActiveVerb
1084 && (fActiveVerb != 1 || counter != fPath.countVerbs())) {
1085 continue;
1086 }
1087 switch (verb) {
1088 case SkPath::kLine_Verb:
1089 nextLast = pts[0] - pts[1];
1090 vector = pts[1] - pts[0];
1091 break;
1092 case SkPath::kQuad_Verb: {
1093 nextLast = pts[1] - pts[2];
1094 if (SkScalarNearlyZero(nextLast.length())) {
1095 nextLast = pts[0] - pts[2];
1096 }
1097 vector = pts[1] - pts[0];
1098 if (SkScalarNearlyZero(vector.length())) {
1099 vector = pts[2] - pts[0];
1100 }
1101 if (!fBisectButton.enabled()) {
1102 break;
1103 }
1104 SkScalar t = SkFindQuadMaxCurvature(pts);
1105 if (0 < t && t < 1) {
1106 SkPoint maxPt = SkEvalQuadAt(pts, t);
1107 SkVector tangent = SkEvalQuadTangentAt(pts, t);
1108 tangent.setLength(fWidthControl.fValLo * 2);
Hal Canary23e474c2017-05-15 13:35:35 -04001109 canvas->drawLine(maxPt, {maxPt.fX + tangent.fY, maxPt.fY - tangent.fX},
1110 fSkeletonPaint);
caryclark64022c12016-05-27 05:13:26 -07001111 }
1112 } break;
1113 case SkPath::kConic_Verb:
1114 nextLast = pts[1] - pts[2];
1115 if (SkScalarNearlyZero(nextLast.length())) {
1116 nextLast = pts[0] - pts[2];
1117 }
1118 vector = pts[1] - pts[0];
1119 if (SkScalarNearlyZero(vector.length())) {
1120 vector = pts[2] - pts[0];
1121 }
1122 if (!fBisectButton.enabled()) {
1123 break;
1124 }
1125 // FIXME : need max curvature or equivalent here
1126 break;
1127 case SkPath::kCubic_Verb: {
1128 nextLast = pts[2] - pts[3];
1129 if (SkScalarNearlyZero(nextLast.length())) {
1130 nextLast = pts[1] - pts[3];
1131 if (SkScalarNearlyZero(nextLast.length())) {
1132 nextLast = pts[0] - pts[3];
1133 }
1134 }
1135 vector = pts[0] - pts[1];
1136 if (SkScalarNearlyZero(vector.length())) {
1137 vector = pts[0] - pts[2];
1138 if (SkScalarNearlyZero(vector.length())) {
1139 vector = pts[0] - pts[3];
1140 }
1141 }
1142 if (!fBisectButton.enabled()) {
1143 break;
1144 }
1145 SkScalar tMax[2];
1146 int tMaxCount = SkFindCubicMaxCurvature(pts, tMax);
1147 for (int tIndex = 0; tIndex < tMaxCount; ++tIndex) {
1148 if (0 >= tMax[tIndex] || tMax[tIndex] >= 1) {
1149 continue;
1150 }
1151 SkPoint maxPt;
1152 SkVector tangent;
Ben Wagnera93a14a2017-08-28 10:34:05 -04001153 SkEvalCubicAt(pts, tMax[tIndex], &maxPt, &tangent, nullptr);
caryclark64022c12016-05-27 05:13:26 -07001154 tangent.setLength(fWidthControl.fValLo * 2);
Hal Canary23e474c2017-05-15 13:35:35 -04001155 canvas->drawLine(maxPt, {maxPt.fX + tangent.fY, maxPt.fY - tangent.fX},
1156 fSkeletonPaint);
caryclark64022c12016-05-27 05:13:26 -07001157 }
1158 } break;
1159 case SkPath::kClose_Verb:
1160 if (foundFirst) {
1161 draw_bisect(canvas, lastVector, firstVector, firstPt);
1162 foundFirst = false;
1163 }
1164 break;
Ben Wagner63fd7602017-10-09 15:45:33 -04001165 default:
caryclark64022c12016-05-27 05:13:26 -07001166 break;
1167 }
1168 if (SkPath::kLine_Verb <= verb && verb <= SkPath::kCubic_Verb) {
1169 if (!foundFirst) {
1170 firstPt = pts[0];
1171 firstVector = vector;
1172 foundFirst = true;
1173 } else {
1174 draw_bisect(canvas, lastVector, vector, pts[0]);
1175 }
1176 lastVector = nextLast;
1177 }
1178 }
1179 }
1180
1181 void draw_legend(SkCanvas* canvas);
1182
1183 void draw_segment(SkCanvas* canvas) {
1184 SkPoint pts[4];
1185 SkPath::Verb verb;
1186 SkPath::Iter iter(fPath, true);
1187 int counter = -1;
1188 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1189 if (++counter < fActiveVerb) {
1190 continue;
1191 }
1192 switch (verb) {
1193 case SkPath::kLine_Verb:
1194 canvas->drawPoints(SkCanvas::kLines_PointMode, 2, pts, fActivePaint);
1195 draw_points(canvas, pts, 2);
1196 break;
1197 case SkPath::kQuad_Verb: {
1198 SkPath qPath;
1199 qPath.moveTo(pts[0]);
1200 qPath.quadTo(pts[1], pts[2]);
1201 canvas->drawPath(qPath, fActivePaint);
1202 draw_points(canvas, pts, 3);
1203 } break;
1204 case SkPath::kConic_Verb: {
1205 SkPath conicPath;
1206 conicPath.moveTo(pts[0]);
1207 conicPath.conicTo(pts[1], pts[2], iter.conicWeight());
1208 canvas->drawPath(conicPath, fActivePaint);
1209 draw_points(canvas, pts, 3);
1210 } break;
1211 case SkPath::kCubic_Verb: {
Cary Clark7eb01e02016-12-08 14:36:32 -05001212 SkScalar loopT[3];
1213 int complex = SkDCubic::ComplexBreak(pts, loopT);
caryclark64022c12016-05-27 05:13:26 -07001214 SkPath cPath;
1215 cPath.moveTo(pts[0]);
1216 cPath.cubicTo(pts[1], pts[2], pts[3]);
1217 canvas->drawPath(cPath, complex ? fComplexPaint : fActivePaint);
1218 draw_points(canvas, pts, 4);
1219 } break;
Ben Wagner63fd7602017-10-09 15:45:33 -04001220 default:
caryclark64022c12016-05-27 05:13:26 -07001221 break;
1222 }
1223 return;
1224 }
1225 }
1226
1227 void draw_points(SkCanvas* canvas, SkPoint* points, int count) {
1228 for (int index = 0; index < count; ++index) {
1229 canvas->drawCircle(points[index].fX, points[index].fY, 10, fPointPaint);
1230 }
1231 }
1232
1233 int hittest_verb(SkPoint pt, SkPath::Verb* verbPtr, SkScalar* weight) {
1234 SkIntersections i;
1235 SkDLine hHit = {{{pt.fX - kHitToleranace, pt.fY }, {pt.fX + kHitToleranace, pt.fY}}};
1236 SkDLine vHit = {{{pt.fX, pt.fY - kHitToleranace }, {pt.fX, pt.fY + kHitToleranace}}};
1237 SkPoint pts[4];
1238 SkPath::Verb verb;
1239 SkPath::Iter iter(fPath, true);
1240 int counter = -1;
1241 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1242 ++counter;
1243 switch (verb) {
1244 case SkPath::kLine_Verb: {
1245 SkDLine line;
1246 line.set(pts);
1247 if (i.intersect(line, hHit) || i.intersect(line, vHit)) {
1248 *verbPtr = verb;
1249 *weight = 1;
1250 return counter;
1251 }
1252 } break;
1253 case SkPath::kQuad_Verb: {
1254 SkDQuad quad;
1255 quad.set(pts);
1256 if (i.intersect(quad, hHit) || i.intersect(quad, vHit)) {
1257 *verbPtr = verb;
1258 *weight = 1;
1259 return counter;
1260 }
1261 } break;
1262 case SkPath::kConic_Verb: {
1263 SkDConic conic;
1264 SkScalar w = iter.conicWeight();
1265 conic.set(pts, w);
1266 if (i.intersect(conic, hHit) || i.intersect(conic, vHit)) {
1267 *verbPtr = verb;
1268 *weight = w;
1269 return counter;
1270 }
1271 } break;
1272 case SkPath::kCubic_Verb: {
1273 SkDCubic cubic;
1274 cubic.set(pts);
1275 if (i.intersect(cubic, hHit) || i.intersect(cubic, vHit)) {
1276 *verbPtr = verb;
1277 *weight = 1;
1278 return counter;
1279 }
1280 } break;
Ben Wagner63fd7602017-10-09 15:45:33 -04001281 default:
caryclark64022c12016-05-27 05:13:26 -07001282 break;
1283 }
1284 }
1285 return -1;
1286 }
1287
1288 SkScalar pt_to_line(SkPoint s, SkPoint e, int x, int y) {
1289 SkScalar radius = fWidthControl.fValLo;
1290 SkVector adjOpp = e - s;
Cary Clarkdf429f32017-11-08 11:44:31 -05001291 SkScalar lenSq = SkPointPriv::LengthSqd(adjOpp);
caryclark64022c12016-05-27 05:13:26 -07001292 SkPoint rotated = {
1293 (y - s.fY) * adjOpp.fY + (x - s.fX) * adjOpp.fX,
1294 (y - s.fY) * adjOpp.fX - (x - s.fX) * adjOpp.fY,
1295 };
1296 if (rotated.fX < 0 || rotated.fX > lenSq) {
1297 return -radius;
1298 }
1299 rotated.fY /= SkScalarSqrt(lenSq);
1300 return SkTMax(-radius, SkTMin(radius, rotated.fY));
1301 }
1302
1303 // given a line, compute the interior and exterior gradient coverage
1304 bool coverage(SkPoint s, SkPoint e, uint8_t* distanceMap, int w, int h) {
1305 SkScalar radius = fWidthControl.fValLo;
1306 int minX = SkTMax(0, (int) (SkTMin(s.fX, e.fX) - radius));
1307 int minY = SkTMax(0, (int) (SkTMin(s.fY, e.fY) - radius));
1308 int maxX = SkTMin(w, (int) (SkTMax(s.fX, e.fX) + radius) + 1);
1309 int maxY = SkTMin(h, (int) (SkTMax(s.fY, e.fY) + radius) + 1);
1310 for (int y = minY; y < maxY; ++y) {
1311 for (int x = minX; x < maxX; ++x) {
1312 SkScalar ptToLineDist = pt_to_line(s, e, x, y);
1313 if (ptToLineDist > -radius && ptToLineDist < radius) {
1314 SkScalar coverage = ptToLineDist / radius;
1315 add_to_map(1 - SkScalarAbs(coverage), x, y, distanceMap, w, h);
1316 }
1317 SkVector ptToS = { x - s.fX, y - s.fY };
1318 SkScalar dist = ptToS.length();
1319 if (dist < radius) {
1320 SkScalar coverage = dist / radius;
1321 add_to_map(1 - SkScalarAbs(coverage), x, y, distanceMap, w, h);
1322 }
1323 SkVector ptToE = { x - e.fX, y - e.fY };
1324 dist = ptToE.length();
1325 if (dist < radius) {
1326 SkScalar coverage = dist / radius;
1327 add_to_map(1 - SkScalarAbs(coverage), x, y, distanceMap, w, h);
1328 }
1329 }
1330 }
1331 return true;
1332 }
1333
1334 void quad_coverage(SkPoint pts[3], uint8_t* distanceMap, int w, int h) {
1335 SkScalar dist = pts[0].Distance(pts[0], pts[2]);
1336 if (dist < gCurveDistance) {
1337 (void) coverage(pts[0], pts[2], distanceMap, w, h);
1338 return;
1339 }
1340 SkPoint split[5];
1341 SkChopQuadAt(pts, split, 0.5f);
1342 quad_coverage(&split[0], distanceMap, w, h);
1343 quad_coverage(&split[2], distanceMap, w, h);
1344 }
1345
1346 void conic_coverage(SkPoint pts[3], SkScalar weight, uint8_t* distanceMap, int w, int h) {
1347 SkScalar dist = pts[0].Distance(pts[0], pts[2]);
1348 if (dist < gCurveDistance) {
1349 (void) coverage(pts[0], pts[2], distanceMap, w, h);
1350 return;
1351 }
1352 SkConic split[2];
1353 SkConic conic;
1354 conic.set(pts, weight);
caryclark414c4292016-09-26 11:03:54 -07001355 if (conic.chopAt(0.5f, split)) {
1356 conic_coverage(split[0].fPts, split[0].fW, distanceMap, w, h);
1357 conic_coverage(split[1].fPts, split[1].fW, distanceMap, w, h);
1358 }
caryclark64022c12016-05-27 05:13:26 -07001359 }
1360
1361 void cubic_coverage(SkPoint pts[4], uint8_t* distanceMap, int w, int h) {
1362 SkScalar dist = pts[0].Distance(pts[0], pts[3]);
1363 if (dist < gCurveDistance) {
1364 (void) coverage(pts[0], pts[3], distanceMap, w, h);
1365 return;
1366 }
1367 SkPoint split[7];
1368 SkChopCubicAt(pts, split, 0.5f);
1369 cubic_coverage(&split[0], distanceMap, w, h);
1370 cubic_coverage(&split[3], distanceMap, w, h);
1371 }
1372
1373 void path_coverage(const SkPath& path, uint8_t* distanceMap, int w, int h) {
1374 memset(distanceMap, 0, sizeof(distanceMap[0]) * w * h);
1375 SkPoint pts[4];
1376 SkPath::Verb verb;
1377 SkPath::Iter iter(path, true);
1378 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1379 switch (verb) {
1380 case SkPath::kLine_Verb:
1381 (void) coverage(pts[0], pts[1], distanceMap, w, h);
1382 break;
1383 case SkPath::kQuad_Verb:
1384 quad_coverage(pts, distanceMap, w, h);
1385 break;
1386 case SkPath::kConic_Verb:
1387 conic_coverage(pts, iter.conicWeight(), distanceMap, w, h);
1388 break;
1389 case SkPath::kCubic_Verb:
1390 cubic_coverage(pts, distanceMap, w, h);
1391 break;
Ben Wagner63fd7602017-10-09 15:45:33 -04001392 default:
caryclark64022c12016-05-27 05:13:26 -07001393 break;
1394 }
1395 }
1396 }
1397
1398 static uint8_t* set_up_dist_map(const SkImageInfo& imageInfo, SkBitmap* distMap) {
1399 distMap->setInfo(imageInfo);
1400 distMap->setIsVolatile(true);
1401 SkAssertResult(distMap->tryAllocPixels());
1402 SkASSERT((int) distMap->rowBytes() == imageInfo.width());
1403 return distMap->getAddr8(0, 0);
1404 }
1405
1406 void path_stroke(int index, SkPath* inner, SkPath* outer) {
1407 #if 0
1408 SkPathStroker stroker(fPath, fWidthControl.fValLo, 0,
1409 SkPaint::kRound_Cap, SkPaint::kRound_Join, fResControl.fValLo);
1410 SkPoint pts[4], firstPt, lastPt;
1411 SkPath::Verb verb;
1412 SkPath::Iter iter(fPath, true);
1413 int counter = -1;
1414 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1415 ++counter;
1416 switch (verb) {
1417 case SkPath::kMove_Verb:
1418 firstPt = pts[0];
1419 break;
1420 case SkPath::kLine_Verb:
1421 if (counter == index) {
1422 stroker.moveTo(pts[0]);
1423 stroker.lineTo(pts[1]);
1424 goto done;
1425 }
1426 lastPt = pts[1];
1427 break;
1428 case SkPath::kQuad_Verb:
1429 if (counter == index) {
1430 stroker.moveTo(pts[0]);
1431 stroker.quadTo(pts[1], pts[2]);
1432 goto done;
1433 }
1434 lastPt = pts[2];
1435 break;
1436 case SkPath::kConic_Verb:
1437 if (counter == index) {
1438 stroker.moveTo(pts[0]);
1439 stroker.conicTo(pts[1], pts[2], iter.conicWeight());
1440 goto done;
1441 }
1442 lastPt = pts[2];
1443 break;
1444 case SkPath::kCubic_Verb:
1445 if (counter == index) {
1446 stroker.moveTo(pts[0]);
1447 stroker.cubicTo(pts[1], pts[2], pts[3]);
1448 goto done;
1449 }
1450 lastPt = pts[3];
1451 break;
1452 case SkPath::kClose_Verb:
1453 if (counter == index) {
1454 stroker.moveTo(lastPt);
1455 stroker.lineTo(firstPt);
1456 goto done;
1457 }
1458 break;
1459 case SkPath::kDone_Verb:
1460 break;
1461 default:
1462 SkASSERT(0);
1463 }
1464 }
1465 done:
1466 *inner = stroker.fInner;
1467 *outer = stroker.fOuter;
1468#endif
1469 }
1470
1471 void draw_stroke(SkCanvas* canvas, int active) {
1472 SkPath inner, outer;
1473 path_stroke(active, &inner, &outer);
1474 canvas->drawPath(inner, fSkeletonPaint);
1475 canvas->drawPath(outer, fSkeletonPaint);
1476 }
1477
1478 void gather_strokes() {
1479 fStrokes.reset();
1480 for (int index = 0; index < fPath.countVerbs(); ++index) {
1481 Stroke& inner = fStrokes.push_back();
1482 inner.reset();
1483 inner.fInner = true;
1484 Stroke& outer = fStrokes.push_back();
1485 outer.reset();
1486 outer.fInner = false;
1487 path_stroke(index, &inner.fPath, &outer.fPath);
1488 }
1489 }
1490
1491 void trim_strokes() {
1492 // eliminate self-itersecting loops
1493 // trim outside edges
1494 gather_strokes();
1495 for (int index = 0; index < fStrokes.count(); ++index) {
1496 SkPath& outPath = fStrokes[index].fPath;
1497 for (int inner = 0; inner < fStrokes.count(); ++inner) {
1498 if (index == inner) {
1499 continue;
1500 }
1501 SkPath& inPath = fStrokes[inner].fPath;
1502 if (!outPath.getBounds().intersects(inPath.getBounds())) {
1503 continue;
1504 }
Ben Wagner63fd7602017-10-09 15:45:33 -04001505
caryclark64022c12016-05-27 05:13:26 -07001506 }
1507 }
1508 }
1509
1510 void onDrawContent(SkCanvas* canvas) override {
1511#if 0
1512 SkDEBUGCODE(SkDebugStrokeGlobals debugGlobals);
1513 SkOpAA aaResult(fPath, fWidthControl.fValLo, fResControl.fValLo
1514 SkDEBUGPARAMS(&debugGlobals));
1515#endif
1516 SkPath strokePath;
1517// aaResult.simplify(&strokePath);
1518 canvas->drawPath(strokePath, fSkeletonPaint);
1519 SkRect bounds = fPath.getBounds();
1520 SkScalar radius = fWidthControl.fValLo;
1521 int w = (int) (bounds.fRight + radius + 1);
1522 int h = (int) (bounds.fBottom + radius + 1);
1523 SkImageInfo imageInfo = SkImageInfo::MakeA8(w, h);
1524 SkBitmap distMap;
1525 uint8_t* distanceMap = set_up_dist_map(imageInfo, &distMap);
1526 path_coverage(fPath, distanceMap, w, h);
1527 if (fFillButton.enabled()) {
1528 canvas->drawPath(fPath, fCoveragePaint);
1529 }
1530 if (fFilterButton.fState == 2
1531 && (0 < fFilterControl.fValLo || fFilterControl.fValHi < 255)) {
1532 SkBitmap filteredMap;
1533 uint8_t* filtered = set_up_dist_map(imageInfo, &filteredMap);
1534 filter_coverage(distanceMap, sizeof(uint8_t) * w * h, (uint8_t) fFilterControl.fValLo,
1535 (uint8_t) fFilterControl.fValHi, filtered);
1536 canvas->drawBitmap(filteredMap, 0, 0, &fCoveragePaint);
1537 } else if (fFilterButton.enabled()) {
1538 canvas->drawBitmap(distMap, 0, 0, &fCoveragePaint);
1539 }
1540 if (fSkeletonButton.enabled()) {
1541 canvas->drawPath(fPath, fActiveVerb >= 0 ? fLightSkeletonPaint : fSkeletonPaint);
1542 }
1543 if (fActiveVerb >= 0) {
1544 draw_segment(canvas);
1545 }
1546 if (fBisectButton.enabled() || fJoinButton.enabled()) {
1547 draw_bisects(canvas, fActiveVerb >= 0);
1548 }
1549 if (fInOutButton.enabled()) {
1550 if (fActiveVerb >= 0) {
1551 draw_stroke(canvas, fActiveVerb);
1552 } else {
1553 for (int index = 0; index < fPath.countVerbs(); ++index) {
1554 draw_stroke(canvas, index);
1555 }
1556 }
1557 }
1558 if (fHideAll) {
1559 return;
1560 }
1561 for (int index = 0; index < kControlCount; ++index) {
1562 kControlList[index].fControl->draw(canvas, fControlPaints);
1563 }
1564 for (int index = 0; index < kButtonCount; ++index) {
1565 kButtonList[index].fButton->draw(canvas, fButtonPaints);
1566 }
1567 if (fShowLegend) {
1568 draw_legend(canvas);
1569 }
1570
1571#if 0
1572 SkPaint paint;
1573 paint.setARGB(255, 34, 31, 31);
1574 paint.setAntiAlias(true);
1575
1576 SkPath path;
1577 path.moveTo(18,439);
1578 path.lineTo(414,439);
1579 path.lineTo(414,702);
1580 path.lineTo(18,702);
1581 path.lineTo(18,439);
1582
1583 path.moveTo(19,701);
1584 path.lineTo(413,701);
1585 path.lineTo(413,440);
1586 path.lineTo(19,440);
1587 path.lineTo(19,701);
1588 path.close();
1589 canvas->drawPath(path, paint);
1590
1591 canvas->scale(1.0f, -1.0f);
1592 canvas->translate(0.0f, -800.0f);
1593 canvas->drawPath(path, paint);
1594#endif
1595
1596 }
1597
1598 int hittest_pt(SkPoint pt) {
1599 for (int index = 0; index < fPath.countPoints(); ++index) {
1600 if (SkPoint::Distance(fPath.getPoint(index), pt) <= kHitToleranace * 2) {
1601 return index;
1602 }
1603 }
1604 return -1;
1605 }
1606
Hal Canaryb1f411a2019-08-29 10:39:22 -04001607 virtual Sample::Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey modi) override {
caryclark64022c12016-05-27 05:13:26 -07001608 SkPoint pt = {x, y};
1609 int ptHit = hittest_pt(pt);
1610 if (ptHit >= 0) {
Hal Canaryfcf63592019-07-12 11:32:43 -04001611 return new MyClick(MyClick::kPtType, ptHit);
caryclark64022c12016-05-27 05:13:26 -07001612 }
1613 SkPath::Verb verb;
1614 SkScalar weight;
1615 int verbHit = hittest_verb(pt, &verb, &weight);
1616 if (verbHit >= 0) {
Hal Canaryfcf63592019-07-12 11:32:43 -04001617 return new MyClick(MyClick::kVerbType, verbHit, verb, weight);
caryclark64022c12016-05-27 05:13:26 -07001618 }
1619 if (!fHideAll) {
1620 const SkRect& rectPt = SkRect::MakeXYWH(x, y, 1, 1);
1621 for (int index = 0; index < kControlCount; ++index) {
1622 if (kControlList[index].fControl->contains(rectPt)) {
Hal Canaryfcf63592019-07-12 11:32:43 -04001623 return new MyClick(MyClick::kControlType,
caryclark64022c12016-05-27 05:13:26 -07001624 kControlList[index].fControlType);
1625 }
1626 }
1627 for (int index = 0; index < kButtonCount; ++index) {
1628 if (kButtonList[index].fButton->contains(rectPt)) {
Hal Canaryfcf63592019-07-12 11:32:43 -04001629 return new MyClick(MyClick::kControlType, kButtonList[index].fButtonType);
caryclark64022c12016-05-27 05:13:26 -07001630 }
1631 }
1632 }
1633 fLineButton.fVisible = fQuadButton.fVisible = fConicButton.fVisible
1634 = fCubicButton.fVisible = fWeightControl.fVisible = fAddButton.fVisible
1635 = fDeleteButton.fVisible = false;
1636 fActiveVerb = -1;
1637 fActivePt = -1;
1638 if (fHandlePathMove) {
Hal Canaryfcf63592019-07-12 11:32:43 -04001639 return new MyClick(MyClick::kPathType, MyClick::kPathMove);
caryclark64022c12016-05-27 05:13:26 -07001640 }
Hal Canaryfcf63592019-07-12 11:32:43 -04001641 return nullptr;
caryclark64022c12016-05-27 05:13:26 -07001642 }
1643
1644 static SkScalar MapScreenYtoValue(int y, const UniControl& control) {
1645 return SkTMin(1.f, SkTMax(0.f,
1646 SkIntToScalar(y) - control.fBounds.fTop) / control.fBounds.height())
1647 * (control.fMax - control.fMin) + control.fMin;
1648 }
1649
1650 bool onClick(Click* click) override {
1651 MyClick* myClick = (MyClick*) click;
1652 switch (myClick->fType) {
1653 case MyClick::kPtType: {
1654 savePath(click->fState);
1655 fActivePt = myClick->ptHit();
1656 SkPoint pt = fPath.getPoint((int) myClick->fControl);
Hal Canaryfcf63592019-07-12 11:32:43 -04001657 pt.offset(SkIntToScalar(click->fCurr.fX - click->fPrev.fX),
1658 SkIntToScalar(click->fCurr.fY - click->fPrev.fY));
caryclark64022c12016-05-27 05:13:26 -07001659 set_path_pt(fActivePt, pt, &fPath);
1660 validatePath();
caryclark64022c12016-05-27 05:13:26 -07001661 return true;
1662 }
1663 case MyClick::kPathType:
1664 savePath(click->fState);
Hal Canaryfcf63592019-07-12 11:32:43 -04001665 fPath.offset(SkIntToScalar(click->fCurr.fX - click->fPrev.fX),
1666 SkIntToScalar(click->fCurr.fY - click->fPrev.fY));
caryclark64022c12016-05-27 05:13:26 -07001667 validatePath();
caryclark64022c12016-05-27 05:13:26 -07001668 return true;
1669 case MyClick::kVerbType: {
1670 fActiveVerb = myClick->verbHit();
1671 fLineButton.fVisible = fQuadButton.fVisible = fConicButton.fVisible
1672 = fCubicButton.fVisible = fAddButton.fVisible = fDeleteButton.fVisible
1673 = true;
1674 fLineButton.setEnabled(myClick->fVerb == SkPath::kLine_Verb);
1675 fQuadButton.setEnabled(myClick->fVerb == SkPath::kQuad_Verb);
1676 fConicButton.setEnabled(myClick->fVerb == SkPath::kConic_Verb);
1677 fCubicButton.setEnabled(myClick->fVerb == SkPath::kCubic_Verb);
1678 fWeightControl.fValLo = myClick->fWeight;
1679 fWeightControl.fVisible = myClick->fVerb == SkPath::kConic_Verb;
1680 } break;
1681 case MyClick::kControlType: {
Hal Canaryb1f411a2019-08-29 10:39:22 -04001682 if (click->fState != skui::InputState::kDown && myClick->isButton()) {
caryclark64022c12016-05-27 05:13:26 -07001683 return true;
1684 }
1685 switch (myClick->fControl) {
1686 case MyClick::kFilterControl: {
Hal Canaryfcf63592019-07-12 11:32:43 -04001687 SkScalar val = MapScreenYtoValue(click->fCurr.fY, fFilterControl);
caryclark64022c12016-05-27 05:13:26 -07001688 if (val - fFilterControl.fValLo < fFilterControl.fValHi - val) {
1689 fFilterControl.fValLo = SkTMax(0.f, val);
1690 } else {
1691 fFilterControl.fValHi = SkTMin(255.f, val);
1692 }
1693 } break;
1694 case MyClick::kResControl:
Hal Canaryfcf63592019-07-12 11:32:43 -04001695 fResControl.fValLo = MapScreenYtoValue(click->fCurr.fY, fResControl);
caryclark64022c12016-05-27 05:13:26 -07001696 break;
1697 case MyClick::kWeightControl: {
1698 savePath(click->fState);
Hal Canaryfcf63592019-07-12 11:32:43 -04001699 SkScalar w = MapScreenYtoValue(click->fCurr.fY, fWeightControl);
caryclark64022c12016-05-27 05:13:26 -07001700 set_path_weight(fActiveVerb, w, &fPath);
1701 validatePath();
1702 fWeightControl.fValLo = w;
1703 } break;
1704 case MyClick::kWidthControl:
Hal Canaryfcf63592019-07-12 11:32:43 -04001705 fWidthControl.fValLo = MapScreenYtoValue(click->fCurr.fY, fWidthControl);
caryclark64022c12016-05-27 05:13:26 -07001706 break;
1707 case MyClick::kLineButton:
1708 savePath(click->fState);
1709 enable_verb_button(myClick->fControl);
1710 fWeightControl.fVisible = false;
1711 set_path_verb(fActiveVerb, SkPath::kLine_Verb, &fPath, 1);
1712 validatePath();
1713 break;
1714 case MyClick::kQuadButton:
1715 savePath(click->fState);
1716 enable_verb_button(myClick->fControl);
1717 fWeightControl.fVisible = false;
1718 set_path_verb(fActiveVerb, SkPath::kQuad_Verb, &fPath, 1);
1719 validatePath();
1720 break;
1721 case MyClick::kConicButton: {
1722 savePath(click->fState);
1723 enable_verb_button(myClick->fControl);
1724 fWeightControl.fVisible = true;
1725 const SkScalar defaultConicWeight = 1.f / SkScalarSqrt(2);
1726 set_path_verb(fActiveVerb, SkPath::kConic_Verb, &fPath, defaultConicWeight);
1727 validatePath();
1728 fWeightControl.fValLo = get_path_weight(fActiveVerb, fPath);
1729 } break;
1730 case MyClick::kCubicButton:
1731 savePath(click->fState);
1732 enable_verb_button(myClick->fControl);
1733 fWeightControl.fVisible = false;
1734 set_path_verb(fActiveVerb, SkPath::kCubic_Verb, &fPath, 1);
1735 validatePath();
1736 break;
1737 case MyClick::kAddButton:
1738 savePath(click->fState);
1739 add_path_segment(fActiveVerb, &fPath);
1740 validatePath();
1741 if (fWeightControl.fVisible) {
1742 fWeightControl.fValLo = get_path_weight(fActiveVerb, fPath);
1743 }
1744 break;
1745 case MyClick::kDeleteButton:
1746 savePath(click->fState);
1747 delete_path_segment(fActiveVerb, &fPath);
1748 validatePath();
1749 break;
1750 case MyClick::kFillButton:
1751 fFillButton.toggle();
1752 break;
1753 case MyClick::kSkeletonButton:
1754 fSkeletonButton.toggle();
1755 break;
1756 case MyClick::kFilterButton:
1757 fFilterButton.toggle();
1758 fFilterControl.fVisible = fFilterButton.fState == 2;
1759 break;
1760 case MyClick::kBisectButton:
1761 fBisectButton.toggle();
1762 break;
1763 case MyClick::kJoinButton:
1764 fJoinButton.toggle();
1765 break;
1766 case MyClick::kInOutButton:
1767 fInOutButton.toggle();
1768 break;
1769 default:
1770 SkASSERT(0);
1771 break;
1772 }
1773 } break;
1774 default:
1775 SkASSERT(0);
1776 break;
1777 }
1778 setControlButtonsPos();
caryclark64022c12016-05-27 05:13:26 -07001779 return true;
1780 }
1781
1782private:
Ben Wagnerb2c4ea62018-08-08 11:36:17 -04001783 typedef Sample INHERITED;
caryclark64022c12016-05-27 05:13:26 -07001784};
1785
1786static struct KeyCommand {
1787 char fKey;
1788 char fAlternate;
1789 const char* fDescriptionL;
1790 const char* fDescriptionR;
1791 bool (AAGeometryView::*fFunction)();
1792} kKeyCommandList[] = {
Ben Wagner63fd7602017-10-09 15:45:33 -04001793 { ' ', 0, "space", "center path", &AAGeometryView::scaleToFit },
1794 { '-', 0, "-", "zoom out", &AAGeometryView::scaleDown },
1795 { '+', '=', "+/=", "zoom in", &AAGeometryView::scaleUp },
Chris Dalton08d1a252017-10-20 11:46:47 -06001796 { 'D', 0, "D", "dump to console", &AAGeometryView::pathDump },
1797 { 'H', 0, "H", "hide controls", &AAGeometryView::hideAll },
1798 { 'R', 0, "R", "reset path", &AAGeometryView::constructPath },
1799 { 'Z', 0, "Z", "undo", &AAGeometryView::undo },
caryclark64022c12016-05-27 05:13:26 -07001800 { '?', 0, "?", "show legend", &AAGeometryView::showLegend },
1801};
1802
1803const int kKeyCommandCount = (int) SK_ARRAY_COUNT(kKeyCommandList);
1804
1805void AAGeometryView::draw_legend(SkCanvas* canvas) {
1806 SkScalar bottomOffset = this->height() - 10;
1807 for (int index = kKeyCommandCount - 1; index >= 0; --index) {
1808 bottomOffset -= 15;
Mike Reedb579f072019-01-03 15:45:53 -05001809 SkTextUtils::DrawString(canvas, kKeyCommandList[index].fDescriptionL, this->width() - 160, bottomOffset,
1810 fLegendLeftFont, SkPaint());
Mike Reeda697df92018-10-26 07:28:30 -04001811 SkTextUtils::DrawString(canvas, kKeyCommandList[index].fDescriptionR,
Cary Clark2a475ea2017-04-28 15:35:12 -04001812 this->width() - 20, bottomOffset,
Mike Reedb579f072019-01-03 15:45:53 -05001813 fLegendRightFont, SkPaint(), SkTextUtils::kRight_Align);
caryclark64022c12016-05-27 05:13:26 -07001814 }
1815}
1816
Hal Canary6cc65e12019-07-03 15:53:04 -04001817bool AAGeometryView::onChar(SkUnichar uni) {
caryclark64022c12016-05-27 05:13:26 -07001818 for (int index = 0; index < kButtonCount; ++index) {
1819 Button* button = kButtonList[index].fButton;
1820 if (button->fVisible && uni == button->fLabel) {
Hal Canaryfcf63592019-07-12 11:32:43 -04001821 MyClick click(MyClick::kControlType, kButtonList[index].fButtonType);
Hal Canaryb1f411a2019-08-29 10:39:22 -04001822 click.fState = skui::InputState::kDown;
caryclark64022c12016-05-27 05:13:26 -07001823 (void) this->onClick(&click);
1824 return true;
1825 }
1826 }
1827 for (int index = 0; index < kKeyCommandCount; ++index) {
1828 KeyCommand& keyCommand = kKeyCommandList[index];
1829 if (uni == keyCommand.fKey || uni == keyCommand.fAlternate) {
1830 return (this->*keyCommand.fFunction)();
1831 }
1832 }
1833 if (('A' <= uni && uni <= 'Z') || ('a' <= uni && uni <= 'z')) {
1834 for (int index = 0; index < kButtonCount; ++index) {
1835 Button* button = kButtonList[index].fButton;
1836 if (button->fVisible && (uni & ~0x20) == (button->fLabel & ~0x20)) {
Hal Canaryfcf63592019-07-12 11:32:43 -04001837 MyClick click(MyClick::kControlType, kButtonList[index].fButtonType);
Hal Canaryb1f411a2019-08-29 10:39:22 -04001838 click.fState = skui::InputState::kDown;
caryclark64022c12016-05-27 05:13:26 -07001839 (void) this->onClick(&click);
1840 return true;
1841 }
1842 }
1843 }
Hal Canary6cc65e12019-07-03 15:53:04 -04001844 return false;
caryclark64022c12016-05-27 05:13:26 -07001845}
Ben Wagner63fd7602017-10-09 15:45:33 -04001846
caryclark64022c12016-05-27 05:13:26 -07001847DEF_SAMPLE( return new AAGeometryView; )