blob: 6865f657a94fa158f18abf6db551a457d3a9df26 [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"
Chris Daltone7ee0692020-01-06 10:18:30 -070018#include "tools/ToolUtils.h"
caryclark64022c12016-05-27 05:13:26 -070019
20#if 0
21void SkStrokeSegment::dump() const {
22 SkDebugf("{{{%1.9g,%1.9g}, {%1.9g,%1.9g}", fPts[0].fX, fPts[0].fY, fPts[1].fX, fPts[1].fY);
23 if (SkPath::kQuad_Verb == fVerb) {
24 SkDebugf(", {%1.9g,%1.9g}", fPts[2].fX, fPts[2].fY);
25 }
26 SkDebugf("}}");
27#ifdef SK_DEBUG
28 SkDebugf(" id=%d", fDebugID);
29#endif
30 SkDebugf("\n");
31}
32
33void SkStrokeSegment::dumpAll() const {
34 const SkStrokeSegment* segment = this;
35 while (segment) {
36 segment->dump();
37 segment = segment->fNext;
38 }
39}
40
41void SkStrokeTriple::dump() const {
42 SkDebugf("{{{%1.9g,%1.9g}, {%1.9g,%1.9g}", fPts[0].fX, fPts[0].fY, fPts[1].fX, fPts[1].fY);
43 if (SkPath::kQuad_Verb <= fVerb) {
44 SkDebugf(", {%1.9g,%1.9g}", fPts[2].fX, fPts[2].fY);
45 }
46 if (SkPath::kCubic_Verb == fVerb) {
47 SkDebugf(", {%1.9g,%1.9g}", fPts[3].fX, fPts[3].fY);
48 } else if (SkPath::kConic_Verb == fVerb) {
49 SkDebugf(", %1.9g", weight());
50 }
51 SkDebugf("}}");
52#ifdef SK_DEBUG
53 SkDebugf(" triple id=%d", fDebugID);
54#endif
55 SkDebugf("\ninner:\n");
56 fInner->dumpAll();
57 SkDebugf("outer:\n");
58 fOuter->dumpAll();
59 SkDebugf("join:\n");
60 fJoin->dumpAll();
61}
62
63void SkStrokeTriple::dumpAll() const {
64 const SkStrokeTriple* triple = this;
65 while (triple) {
66 triple->dump();
67 triple = triple->fNext;
68 }
69}
70
71void SkStrokeContour::dump() const {
72#ifdef SK_DEBUG
73 SkDebugf("id=%d ", fDebugID);
74#endif
75 SkDebugf("head:\n");
76 fHead->dumpAll();
77 SkDebugf("head cap:\n");
78 fHeadCap->dumpAll();
79 SkDebugf("tail cap:\n");
80 fTailCap->dumpAll();
81}
82
83void SkStrokeContour::dumpAll() const {
84 const SkStrokeContour* contour = this;
85 while (contour) {
86 contour->dump();
87 contour = contour->fNext;
88 }
89}
90#endif
91
92SkScalar gCurveDistance = 10;
93
94#if 0 // unused
95static SkPath::Verb get_path_verb(int index, const SkPath& path) {
96 if (index < 0) {
97 return SkPath::kMove_Verb;
98 }
99 SkPoint pts[4];
100 SkPath::Verb verb;
101 SkPath::Iter iter(path, true);
102 int counter = -1;
103 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
104 if (++counter < index) {
105 continue;
106 }
107 return verb;
108 }
109 SkASSERT(0);
110 return SkPath::kMove_Verb;
111}
112#endif
113
114static SkScalar get_path_weight(int index, const SkPath& path) {
115 SkPoint pts[4];
116 SkPath::Verb verb;
117 SkPath::Iter iter(path, true);
118 int counter = -1;
119 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
120 if (++counter < index) {
121 continue;
122 }
123 return verb == SkPath::kConic_Verb ? iter.conicWeight() : 1;
124 }
125 SkASSERT(0);
126 return 0;
127}
128
caryclark64022c12016-05-27 05:13:26 -0700129static void add_path_segment(int index, SkPath* path) {
130 SkPath result;
131 SkPoint pts[4];
132 SkPoint firstPt = { 0, 0 }; // init to avoid warning
133 SkPoint lastPt = { 0, 0 }; // init to avoid warning
134 SkPath::Verb verb;
135 SkPath::RawIter iter(*path);
136 int counter = -1;
137 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
138 SkScalar weight SK_INIT_TO_AVOID_WARNING;
139 if (++counter == index) {
140 switch (verb) {
141 case SkPath::kLine_Verb:
142 result.lineTo((pts[0].fX + pts[1].fX) / 2, (pts[0].fY + pts[1].fY) / 2);
143 break;
144 case SkPath::kQuad_Verb: {
145 SkPoint chop[5];
146 SkChopQuadAtHalf(pts, chop);
147 result.quadTo(chop[1], chop[2]);
148 pts[1] = chop[3];
149 } break;
150 case SkPath::kConic_Verb: {
151 SkConic chop[2];
152 SkConic conic;
153 conic.set(pts, iter.conicWeight());
caryclark414c4292016-09-26 11:03:54 -0700154 if (!conic.chopAt(0.5f, chop)) {
155 return;
156 }
caryclark64022c12016-05-27 05:13:26 -0700157 result.conicTo(chop[0].fPts[1], chop[0].fPts[2], chop[0].fW);
158 pts[1] = chop[1].fPts[1];
159 weight = chop[1].fW;
160 } break;
161 case SkPath::kCubic_Verb: {
162 SkPoint chop[7];
163 SkChopCubicAtHalf(pts, chop);
164 result.cubicTo(chop[1], chop[2], chop[3]);
165 pts[1] = chop[4];
166 pts[2] = chop[5];
167 } break;
168 case SkPath::kClose_Verb: {
169 result.lineTo((lastPt.fX + firstPt.fX) / 2, (lastPt.fY + firstPt.fY) / 2);
170 } break;
171 default:
172 SkASSERT(0);
173 }
174 } else if (verb == SkPath::kConic_Verb) {
175 weight = iter.conicWeight();
176 }
177 switch (verb) {
178 case SkPath::kMove_Verb:
179 result.moveTo(firstPt = pts[0]);
180 break;
181 case SkPath::kLine_Verb:
182 result.lineTo(lastPt = pts[1]);
183 break;
184 case SkPath::kQuad_Verb:
185 result.quadTo(pts[1], lastPt = pts[2]);
186 break;
187 case SkPath::kConic_Verb:
188 result.conicTo(pts[1], lastPt = pts[2], weight);
189 break;
190 case SkPath::kCubic_Verb:
191 result.cubicTo(pts[1], pts[2], lastPt = pts[3]);
192 break;
193 case SkPath::kClose_Verb:
194 result.close();
195 break;
196 case SkPath::kDone_Verb:
197 break;
198 default:
199 SkASSERT(0);
200 }
201 }
202 *path = result;
203}
204
205static void delete_path_segment(int index, SkPath* path) {
206 SkPath result;
207 SkPoint pts[4];
208 SkPath::Verb verb;
209 SkPath::RawIter iter(*path);
210 int counter = -1;
211 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
212 if (++counter == index) {
213 continue;
214 }
215 switch (verb) {
216 case SkPath::kMove_Verb:
217 result.moveTo(pts[0]);
218 break;
219 case SkPath::kLine_Verb:
220 result.lineTo(pts[1]);
221 break;
222 case SkPath::kQuad_Verb:
223 result.quadTo(pts[1], pts[2]);
224 break;
225 case SkPath::kConic_Verb:
226 result.conicTo(pts[1], pts[2], iter.conicWeight());
227 break;
228 case SkPath::kCubic_Verb:
229 result.cubicTo(pts[1], pts[2], pts[3]);
230 break;
231 case SkPath::kClose_Verb:
232 result.close();
233 break;
234 case SkPath::kDone_Verb:
235 break;
236 default:
237 SkASSERT(0);
238 }
239 }
240 *path = result;
241}
242
243static void set_path_weight(int index, SkScalar w, SkPath* path) {
244 SkPath result;
245 SkPoint pts[4];
246 SkPath::Verb verb;
247 SkPath::Iter iter(*path, true);
248 int counter = -1;
249 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
250 ++counter;
251 switch (verb) {
252 case SkPath::kMove_Verb:
253 result.moveTo(pts[0]);
254 break;
255 case SkPath::kLine_Verb:
256 result.lineTo(pts[1]);
257 break;
258 case SkPath::kQuad_Verb:
259 result.quadTo(pts[1], pts[2]);
260 break;
261 case SkPath::kConic_Verb:
262 result.conicTo(pts[1], pts[2], counter == index ? w : iter.conicWeight());
263 break;
264 case SkPath::kCubic_Verb:
265 result.cubicTo(pts[1], pts[2], pts[3]);
266 break;
267 case SkPath::kClose_Verb:
268 result.close();
269 break;
270 case SkPath::kDone_Verb:
271 break;
272 default:
273 SkASSERT(0);
274 }
275 }
276 *path = result;
277}
278
279static void set_path_verb(int index, SkPath::Verb v, SkPath* path, SkScalar w) {
280 SkASSERT(SkPath::kLine_Verb <= v && v <= SkPath::kCubic_Verb);
281 SkPath result;
282 SkPoint pts[4];
283 SkPath::Verb verb;
284 SkPath::Iter iter(*path, true);
285 int counter = -1;
286 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
287 SkScalar weight = verb == SkPath::kConic_Verb ? iter.conicWeight() : 1;
288 if (++counter == index && v != verb) {
289 SkASSERT(SkPath::kLine_Verb <= verb && verb <= SkPath::kCubic_Verb);
290 switch (verb) {
291 case SkPath::kLine_Verb:
292 switch (v) {
293 case SkPath::kConic_Verb:
294 weight = w;
295 case SkPath::kQuad_Verb:
296 pts[2] = pts[1];
297 pts[1].fX = (pts[0].fX + pts[2].fX) / 2;
298 pts[1].fY = (pts[0].fY + pts[2].fY) / 2;
299 break;
300 case SkPath::kCubic_Verb:
301 pts[3] = pts[1];
302 pts[1].fX = (pts[0].fX * 2 + pts[3].fX) / 3;
303 pts[1].fY = (pts[0].fY * 2 + pts[3].fY) / 3;
304 pts[2].fX = (pts[0].fX + pts[3].fX * 2) / 3;
305 pts[2].fY = (pts[0].fY + pts[3].fY * 2) / 3;
306 break;
307 default:
308 SkASSERT(0);
309 break;
310 }
311 break;
312 case SkPath::kQuad_Verb:
313 case SkPath::kConic_Verb:
314 switch (v) {
315 case SkPath::kLine_Verb:
316 pts[1] = pts[2];
317 break;
318 case SkPath::kConic_Verb:
319 weight = w;
320 case SkPath::kQuad_Verb:
321 break;
322 case SkPath::kCubic_Verb: {
323 SkDQuad dQuad;
324 dQuad.set(pts);
325 SkDCubic dCubic = dQuad.debugToCubic();
326 pts[3] = pts[2];
327 pts[1] = dCubic[1].asSkPoint();
328 pts[2] = dCubic[2].asSkPoint();
329 } break;
330 default:
331 SkASSERT(0);
332 break;
333 }
334 break;
335 case SkPath::kCubic_Verb:
336 switch (v) {
337 case SkPath::kLine_Verb:
338 pts[1] = pts[3];
339 break;
340 case SkPath::kConic_Verb:
341 weight = w;
342 case SkPath::kQuad_Verb: {
343 SkDCubic dCubic;
344 dCubic.set(pts);
345 SkDQuad dQuad = dCubic.toQuad();
346 pts[1] = dQuad[1].asSkPoint();
347 pts[2] = pts[3];
348 } break;
349 default:
350 SkASSERT(0);
351 break;
352 }
353 break;
354 default:
355 SkASSERT(0);
356 break;
357 }
358 verb = v;
359 }
360 switch (verb) {
361 case SkPath::kMove_Verb:
362 result.moveTo(pts[0]);
363 break;
364 case SkPath::kLine_Verb:
365 result.lineTo(pts[1]);
366 break;
367 case SkPath::kQuad_Verb:
368 result.quadTo(pts[1], pts[2]);
369 break;
370 case SkPath::kConic_Verb:
371 result.conicTo(pts[1], pts[2], weight);
372 break;
373 case SkPath::kCubic_Verb:
374 result.cubicTo(pts[1], pts[2], pts[3]);
375 break;
376 case SkPath::kClose_Verb:
377 result.close();
378 break;
379 default:
380 SkASSERT(0);
381 break;
382 }
383 }
384 *path = result;
385}
386
387static void add_to_map(SkScalar coverage, int x, int y, uint8_t* distanceMap, int w, int h) {
388 int byteCoverage = (int) (coverage * 256);
389 if (byteCoverage < 0) {
390 byteCoverage = 0;
391 } else if (byteCoverage > 255) {
392 byteCoverage = 255;
393 }
394 SkASSERT(x < w);
395 SkASSERT(y < h);
Brian Osman788b9162020-02-07 10:36:46 -0500396 distanceMap[y * w + x] = std::max(distanceMap[y * w + x], (uint8_t) byteCoverage);
caryclark64022c12016-05-27 05:13:26 -0700397}
398
399static void filter_coverage(const uint8_t* map, int len, uint8_t min, uint8_t max,
400 uint8_t* filter) {
401 for (int index = 0; index < len; ++index) {
402 uint8_t in = map[index];
403 filter[index] = in < min ? 0 : max < in ? 0 : in;
404 }
405}
406
407static void construct_path(SkPath& path) {
408 path.reset();
409 path.moveTo(442, 101.5f);
410 path.quadTo(413.5f, 691, 772, 514);
411 path.lineTo(346, 721.5f);
412 path.lineTo(154, 209);
413 path.lineTo(442, 101.5f);
414 path.close();
415}
416
417struct ButtonPaints {
418 static const int kMaxStateCount = 3;
419 SkPaint fDisabled;
420 SkPaint fStates[kMaxStateCount];
Mike Reedb579f072019-01-03 15:45:53 -0500421 SkFont fLabelFont;
caryclark64022c12016-05-27 05:13:26 -0700422
423 ButtonPaints() {
424 fStates[0].setAntiAlias(true);
425 fStates[0].setStyle(SkPaint::kStroke_Style);
426 fStates[0].setColor(0xFF3F0000);
427 fStates[1] = fStates[0];
428 fStates[1].setStrokeWidth(3);
429 fStates[2] = fStates[1];
430 fStates[2].setColor(0xFFcf0000);
Mike Reedb579f072019-01-03 15:45:53 -0500431 fLabelFont.setSize(25.0f);
caryclark64022c12016-05-27 05:13:26 -0700432 }
433};
434
435struct Button {
436 SkRect fBounds;
437 int fStateCount;
438 int fState;
439 char fLabel;
440 bool fVisible;
441
442 Button(char label) {
443 fStateCount = 2;
444 fState = 0;
445 fLabel = label;
446 fVisible = false;
447 }
448
449 Button(char label, int stateCount) {
450 SkASSERT(stateCount <= ButtonPaints::kMaxStateCount);
451 fStateCount = stateCount;
452 fState = 0;
453 fLabel = label;
454 fVisible = false;
455 }
456
457 bool contains(const SkRect& rect) {
458 return fVisible && fBounds.contains(rect);
459 }
460
461 bool enabled() {
462 return SkToBool(fState);
463 }
464
465 void draw(SkCanvas* canvas, const ButtonPaints& paints) {
466 if (!fVisible) {
467 return;
468 }
469 canvas->drawRect(fBounds, paints.fStates[fState]);
Ben Wagner51e15a62019-05-07 15:38:46 -0400470 SkTextUtils::Draw(canvas, &fLabel, 1, SkTextEncoding::kUTF8, fBounds.centerX(), fBounds.fBottom - 5,
Mike Reedb579f072019-01-03 15:45:53 -0500471 paints.fLabelFont, SkPaint(), SkTextUtils::kCenter_Align);
caryclark64022c12016-05-27 05:13:26 -0700472 }
473
474 void toggle() {
475 if (++fState == fStateCount) {
476 fState = 0;
477 }
478 }
479
480 void setEnabled(bool enabled) {
481 fState = (int) enabled;
482 }
483};
484
485struct ControlPaints {
486 SkPaint fOutline;
487 SkPaint fIndicator;
488 SkPaint fFill;
489 SkPaint fLabel;
490 SkPaint fValue;
491
Mike Reed89126e42019-01-03 12:59:14 -0500492 SkFont fLabelFont;
493 SkFont fValueFont;
494
caryclark64022c12016-05-27 05:13:26 -0700495 ControlPaints() {
496 fOutline.setAntiAlias(true);
497 fOutline.setStyle(SkPaint::kStroke_Style);
498 fIndicator = fOutline;
499 fIndicator.setColor(SK_ColorRED);
500 fFill.setAntiAlias(true);
501 fFill.setColor(0x7fff0000);
502 fLabel.setAntiAlias(true);
Mike Reed89126e42019-01-03 12:59:14 -0500503 fLabelFont.setSize(13.0f);
caryclark64022c12016-05-27 05:13:26 -0700504 fValue.setAntiAlias(true);
Mike Reed89126e42019-01-03 12:59:14 -0500505 fValueFont.setSize(11.0f);
caryclark64022c12016-05-27 05:13:26 -0700506 }
507};
508
509struct UniControl {
510 SkString fName;
511 SkRect fBounds;
512 SkScalar fMin;
513 SkScalar fMax;
514 SkScalar fValLo;
515 SkScalar fYLo;
516 bool fVisible;
517
518 UniControl(const char* name, SkScalar min, SkScalar max) {
519 fName = name;
520 fValLo = fMin = min;
521 fMax = max;
522 fVisible = false;
523
524 }
525
526 virtual ~UniControl() {}
527
528 bool contains(const SkRect& rect) {
529 return fVisible && fBounds.contains(rect);
530 }
531
532 virtual void draw(SkCanvas* canvas, const ControlPaints& paints) {
533 if (!fVisible) {
534 return;
535 }
536 canvas->drawRect(fBounds, paints.fOutline);
537 fYLo = fBounds.fTop + (fValLo - fMin) * fBounds.height() / (fMax - fMin);
538 canvas->drawLine(fBounds.fLeft - 5, fYLo, fBounds.fRight + 5, fYLo, paints.fIndicator);
539 SkString label;
540 label.printf("%0.3g", fValLo);
Hal Canary89a644b2019-01-07 09:36:09 -0500541 canvas->drawString(label, fBounds.fLeft + 5, fYLo - 5, paints.fValueFont, paints.fValue);
542 canvas->drawString(fName, fBounds.fLeft, fBounds.bottom() + 11, paints.fLabelFont,
543 paints.fLabel);
caryclark64022c12016-05-27 05:13:26 -0700544 }
545};
546
547struct BiControl : public UniControl {
548 SkScalar fValHi;
549
Ben Wagner63fd7602017-10-09 15:45:33 -0400550 BiControl(const char* name, SkScalar min, SkScalar max)
caryclark64022c12016-05-27 05:13:26 -0700551 : UniControl(name, min, max)
552 , fValHi(fMax) {
553 }
Ben Wagner63fd7602017-10-09 15:45:33 -0400554
caryclark64022c12016-05-27 05:13:26 -0700555 virtual ~BiControl() {}
556
557 virtual void draw(SkCanvas* canvas, const ControlPaints& paints) {
558 UniControl::draw(canvas, paints);
559 if (!fVisible || fValHi == fValLo) {
560 return;
561 }
562 SkScalar yPos = fBounds.fTop + (fValHi - fMin) * fBounds.height() / (fMax - fMin);
563 canvas->drawLine(fBounds.fLeft - 5, yPos, fBounds.fRight + 5, yPos, paints.fIndicator);
564 SkString label;
565 label.printf("%0.3g", fValHi);
566 if (yPos < fYLo + 10) {
567 yPos = fYLo + 10;
568 }
Hal Canary4484b8f2019-01-08 14:00:08 -0500569 canvas->drawString(label, fBounds.fLeft + 5, yPos - 5, paints.fValueFont, paints.fValue);
caryclark64022c12016-05-27 05:13:26 -0700570 SkRect fill = { fBounds.fLeft, fYLo, fBounds.fRight, yPos };
571 canvas->drawRect(fill, paints.fFill);
572 }
573};
574
575
Ben Wagnerb2c4ea62018-08-08 11:36:17 -0400576class MyClick : public Sample::Click {
caryclark64022c12016-05-27 05:13:26 -0700577public:
578 enum ClickType {
579 kInvalidType = -1,
580 kPtType,
581 kVerbType,
582 kControlType,
583 kPathType,
584 } fType;
585
586 enum ControlType {
587 kInvalidControl = -1,
588 kFirstControl,
589 kFilterControl = kFirstControl,
590 kResControl,
591 kWeightControl,
592 kWidthControl,
593 kLastControl = kWidthControl,
594 kFirstButton,
595 kCubicButton = kFirstButton,
596 kConicButton,
597 kQuadButton,
598 kLineButton,
599 kLastVerbButton = kLineButton,
600 kAddButton,
601 kDeleteButton,
602 kInOutButton,
603 kFillButton,
604 kSkeletonButton,
605 kFilterButton,
606 kBisectButton,
607 kJoinButton,
608 kLastButton = kJoinButton,
609 kPathMove,
610 } fControl;
611
612 SkPath::Verb fVerb;
613 SkScalar fWeight;
614
Hal Canaryfcf63592019-07-12 11:32:43 -0400615 MyClick(ClickType type, ControlType control)
616 : fType(type)
caryclark64022c12016-05-27 05:13:26 -0700617 , fControl(control)
618 , fVerb((SkPath::Verb) -1)
619 , fWeight(1) {
620 }
621
Hal Canaryfcf63592019-07-12 11:32:43 -0400622 MyClick(ClickType type, int index)
623 : fType(type)
caryclark64022c12016-05-27 05:13:26 -0700624 , fControl((ControlType) index)
625 , fVerb((SkPath::Verb) -1)
626 , fWeight(1) {
627 }
628
Hal Canaryfcf63592019-07-12 11:32:43 -0400629 MyClick(ClickType type, int index, SkPath::Verb verb, SkScalar weight)
630 : fType(type)
caryclark64022c12016-05-27 05:13:26 -0700631 , fControl((ControlType) index)
632 , fVerb(verb)
633 , fWeight(weight) {
634 }
635
636 bool isButton() {
637 return kFirstButton <= fControl && fControl <= kLastButton;
638 }
639
640 int ptHit() const {
641 SkASSERT(fType == kPtType);
642 return (int) fControl;
643 }
644
645 int verbHit() const {
646 SkASSERT(fType == kVerbType);
647 return (int) fControl;
648 }
649};
650
651enum {
652 kControlCount = MyClick::kLastControl - MyClick::kFirstControl + 1,
653};
654
655static struct ControlPair {
656 UniControl* fControl;
657 MyClick::ControlType fControlType;
658} kControlList[kControlCount];
659
660enum {
661 kButtonCount = MyClick::kLastButton - MyClick::kFirstButton + 1,
662 kVerbCount = MyClick::kLastVerbButton - MyClick::kFirstButton + 1,
663};
664
665static struct ButtonPair {
666 Button* fButton;
667 MyClick::ControlType fButtonType;
668} kButtonList[kButtonCount];
669
670static void enable_verb_button(MyClick::ControlType type) {
671 for (int index = 0; index < kButtonCount; ++index) {
672 MyClick::ControlType testType = kButtonList[index].fButtonType;
673 if (MyClick::kFirstButton <= testType && testType <= MyClick::kLastVerbButton) {
674 Button* button = kButtonList[index].fButton;
675 button->setEnabled(testType == type);
676 }
677 }
678}
679
680struct Stroke;
681
682struct Active {
683 Active* fNext;
684 Stroke* fParent;
685 SkScalar fStart;
686 SkScalar fEnd;
687
688 void reset() {
Ben Wagnera93a14a2017-08-28 10:34:05 -0400689 fNext = nullptr;
caryclark64022c12016-05-27 05:13:26 -0700690 fStart = 0;
691 fEnd = 1;
692 }
693};
694
695struct Stroke {
696 SkPath fPath;
697 Active fActive;
698 bool fInner;
699
700 void reset() {
701 fPath.reset();
702 fActive.reset();
703 }
704};
705
706struct PathUndo {
707 SkPath fPath;
Hal Canary8918d532019-07-12 10:04:14 -0400708 std::unique_ptr<PathUndo> fNext;
caryclark64022c12016-05-27 05:13:26 -0700709};
710
Ben Wagnerb2c4ea62018-08-08 11:36:17 -0400711class AAGeometryView : public Sample {
caryclark64022c12016-05-27 05:13:26 -0700712 SkPaint fActivePaint;
713 SkPaint fComplexPaint;
714 SkPaint fCoveragePaint;
Mike Reedb579f072019-01-03 15:45:53 -0500715 SkFont fLegendLeftFont;
716 SkFont fLegendRightFont;
caryclark64022c12016-05-27 05:13:26 -0700717 SkPaint fPointPaint;
718 SkPaint fSkeletonPaint;
719 SkPaint fLightSkeletonPaint;
720 SkPath fPath;
721 ControlPaints fControlPaints;
722 UniControl fResControl;
723 UniControl fWeightControl;
724 UniControl fWidthControl;
725 BiControl fFilterControl;
726 ButtonPaints fButtonPaints;
727 Button fCubicButton;
728 Button fConicButton;
729 Button fQuadButton;
730 Button fLineButton;
731 Button fAddButton;
732 Button fDeleteButton;
733 Button fFillButton;
734 Button fSkeletonButton;
735 Button fFilterButton;
736 Button fBisectButton;
737 Button fJoinButton;
738 Button fInOutButton;
739 SkTArray<Stroke> fStrokes;
Hal Canary8918d532019-07-12 10:04:14 -0400740 std::unique_ptr<PathUndo> fUndo;
caryclark64022c12016-05-27 05:13:26 -0700741 int fActivePt;
742 int fActiveVerb;
743 bool fHandlePathMove;
744 bool fShowLegend;
745 bool fHideAll;
Chris Dalton08d1a252017-10-20 11:46:47 -0600746 const int kHitToleranace = 25;
caryclark64022c12016-05-27 05:13:26 -0700747
748public:
749
Ben Wagner63fd7602017-10-09 15:45:33 -0400750 AAGeometryView()
caryclark64022c12016-05-27 05:13:26 -0700751 : fResControl("error", 0, 10)
752 , fWeightControl("weight", 0, 5)
753 , fWidthControl("width", FLT_EPSILON, 100)
754 , fFilterControl("filter", 0, 255)
755 , fCubicButton('C')
756 , fConicButton('K')
757 , fQuadButton('Q')
758 , fLineButton('L')
759 , fAddButton('+')
760 , fDeleteButton('x')
761 , fFillButton('p')
762 , fSkeletonButton('s')
763 , fFilterButton('f', 3)
764 , fBisectButton('b')
765 , fJoinButton('j')
766 , fInOutButton('|')
caryclark64022c12016-05-27 05:13:26 -0700767 , fActivePt(-1)
768 , fActiveVerb(-1)
769 , fHandlePathMove(true)
770 , fShowLegend(false)
771 , fHideAll(false)
772 {
773 fCoveragePaint.setAntiAlias(true);
774 fCoveragePaint.setColor(SK_ColorBLUE);
775 SkPaint strokePaint;
776 strokePaint.setAntiAlias(true);
777 strokePaint.setStyle(SkPaint::kStroke_Style);
778 fPointPaint = strokePaint;
779 fPointPaint.setColor(0x99ee3300);
780 fSkeletonPaint = strokePaint;
781 fSkeletonPaint.setColor(SK_ColorRED);
782 fLightSkeletonPaint = fSkeletonPaint;
783 fLightSkeletonPaint.setColor(0xFFFF7f7f);
784 fActivePaint = strokePaint;
785 fActivePaint.setColor(0x99ee3300);
786 fActivePaint.setStrokeWidth(5);
787 fComplexPaint = fActivePaint;
788 fComplexPaint.setColor(SK_ColorBLUE);
Mike Reedb579f072019-01-03 15:45:53 -0500789 fLegendLeftFont.setSize(13);
790 fLegendRightFont = fLegendLeftFont;
caryclark64022c12016-05-27 05:13:26 -0700791 construct_path(fPath);
792 fFillButton.fVisible = fSkeletonButton.fVisible = fFilterButton.fVisible
793 = fBisectButton.fVisible = fJoinButton.fVisible = fInOutButton.fVisible = true;
794 fSkeletonButton.setEnabled(true);
795 fInOutButton.setEnabled(true);
796 fJoinButton.setEnabled(true);
797 fFilterControl.fValLo = 120;
798 fFilterControl.fValHi = 141;
799 fFilterControl.fVisible = fFilterButton.fState == 2;
800 fResControl.fValLo = 5;
801 fResControl.fVisible = true;
802 fWidthControl.fValLo = 50;
803 fWidthControl.fVisible = true;
804 init_controlList();
805 init_buttonList();
806 }
807
Hal Canary8918d532019-07-12 10:04:14 -0400808 ~AAGeometryView() override {
809 // Free linked list without deep recursion.
810 std::unique_ptr<PathUndo> undo = std::move(fUndo);
811 while (undo) {
812 undo = std::move(undo->fNext);
813 }
814 }
815
caryclark64022c12016-05-27 05:13:26 -0700816 bool constructPath() {
817 construct_path(fPath);
caryclark64022c12016-05-27 05:13:26 -0700818 return true;
819 }
820
Hal Canaryb1f411a2019-08-29 10:39:22 -0400821 void savePath(skui::InputState state) {
822 if (state != skui::InputState::kDown) {
caryclark64022c12016-05-27 05:13:26 -0700823 return;
824 }
825 if (fUndo && fUndo->fPath == fPath) {
826 return;
827 }
Hal Canary8918d532019-07-12 10:04:14 -0400828 std::unique_ptr<PathUndo> undo(new PathUndo);
caryclark64022c12016-05-27 05:13:26 -0700829 undo->fPath = fPath;
Hal Canary8918d532019-07-12 10:04:14 -0400830 undo->fNext = std::move(fUndo);
831 fUndo = std::move(undo);
caryclark64022c12016-05-27 05:13:26 -0700832 }
833
834 bool undo() {
835 if (!fUndo) {
836 return false;
837 }
Hal Canary8918d532019-07-12 10:04:14 -0400838 fPath = std::move(fUndo->fPath);
839 fUndo = std::move(fUndo->fNext);
caryclark64022c12016-05-27 05:13:26 -0700840 validatePath();
caryclark64022c12016-05-27 05:13:26 -0700841 return true;
842 }
843
Hal Canary8918d532019-07-12 10:04:14 -0400844 void validatePath() {}
caryclark64022c12016-05-27 05:13:26 -0700845
846 void set_controlList(int index, UniControl* control, MyClick::ControlType type) {
847 kControlList[index].fControl = control;
848 kControlList[index].fControlType = type;
849 }
850
851 #define SET_CONTROL(Name) set_controlList(index++, &f##Name##Control, \
852 MyClick::k##Name##Control)
853
854 bool hideAll() {
855 fHideAll ^= true;
caryclark64022c12016-05-27 05:13:26 -0700856 return true;
857 }
858
859 void init_controlList() {
860 int index = 0;
861 SET_CONTROL(Width);
862 SET_CONTROL(Res);
863 SET_CONTROL(Filter);
864 SET_CONTROL(Weight);
Brian Osman16adfa32016-10-18 14:42:44 -0400865 }
Ben Wagner63fd7602017-10-09 15:45:33 -0400866
caryclark64022c12016-05-27 05:13:26 -0700867 #undef SET_CONTROL
868
869 void set_buttonList(int index, Button* button, MyClick::ControlType type) {
870 kButtonList[index].fButton = button;
871 kButtonList[index].fButtonType = type;
872 }
873
874 #define SET_BUTTON(Name) set_buttonList(index++, &f##Name##Button, \
875 MyClick::k##Name##Button)
876
877 void init_buttonList() {
878 int index = 0;
879 SET_BUTTON(Fill);
880 SET_BUTTON(Skeleton);
881 SET_BUTTON(Filter);
882 SET_BUTTON(Bisect);
883 SET_BUTTON(Join);
884 SET_BUTTON(InOut);
885 SET_BUTTON(Cubic);
886 SET_BUTTON(Conic);
887 SET_BUTTON(Quad);
888 SET_BUTTON(Line);
889 SET_BUTTON(Add);
890 SET_BUTTON(Delete);
891 }
892
893 #undef SET_BUTTON
894
Hal Canary8a027312019-07-03 10:55:44 -0400895 SkString name() override { return SkString("AAGeometry"); }
896
Hal Canary6cc65e12019-07-03 15:53:04 -0400897 bool onChar(SkUnichar) override;
Ben Wagner63fd7602017-10-09 15:45:33 -0400898
caryclark64022c12016-05-27 05:13:26 -0700899 void onSizeChange() override {
900 setControlButtonsPos();
901 this->INHERITED::onSizeChange();
902 }
903
904 bool pathDump() {
905 fPath.dump();
906 return true;
907 }
908
909 bool scaleDown() {
910 SkMatrix matrix;
911 SkRect bounds = fPath.getBounds();
912 matrix.setScale(1.f / 1.5f, 1.f / 1.5f, bounds.centerX(), bounds.centerY());
913 fPath.transform(matrix);
914 validatePath();
caryclark64022c12016-05-27 05:13:26 -0700915 return true;
916 }
917
918 bool scaleToFit() {
919 SkMatrix matrix;
920 SkRect bounds = fPath.getBounds();
Brian Osman788b9162020-02-07 10:36:46 -0500921 SkScalar scale = std::min(this->width() / bounds.width(), this->height() / bounds.height())
caryclark64022c12016-05-27 05:13:26 -0700922 * 0.8f;
923 matrix.setScale(scale, scale, bounds.centerX(), bounds.centerY());
924 fPath.transform(matrix);
925 bounds = fPath.getBounds();
926 SkScalar offsetX = (this->width() - bounds.width()) / 2 - bounds.fLeft;
927 SkScalar offsetY = (this->height() - bounds.height()) / 2 - bounds.fTop;
928 fPath.offset(offsetX, offsetY);
929 validatePath();
caryclark64022c12016-05-27 05:13:26 -0700930 return true;
931 }
932
933 bool scaleUp() {
934 SkMatrix matrix;
935 SkRect bounds = fPath.getBounds();
936 matrix.setScale(1.5f, 1.5f, bounds.centerX(), bounds.centerY());
937 fPath.transform(matrix);
938 validatePath();
caryclark64022c12016-05-27 05:13:26 -0700939 return true;
940 }
941
942 void setControlButtonsPos() {
943 SkScalar widthOffset = this->width() - 100;
944 for (int index = 0; index < kControlCount; ++index) {
945 if (kControlList[index].fControl->fVisible) {
946 kControlList[index].fControl->fBounds.setXYWH(widthOffset, 30, 30, 400);
947 widthOffset -= 50;
948 }
949 }
950 SkScalar buttonOffset = 0;
951 for (int index = 0; index < kButtonCount; ++index) {
952 kButtonList[index].fButton->fBounds.setXYWH(this->width() - 50,
953 buttonOffset += 50, 30, 30);
954 }
955 }
956
957 bool showLegend() {
958 fShowLegend ^= true;
caryclark64022c12016-05-27 05:13:26 -0700959 return true;
960 }
961
962 void draw_bisect(SkCanvas* canvas, const SkVector& lastVector, const SkVector& vector,
963 const SkPoint& pt) {
964 SkVector lastV = lastVector;
965 SkScalar lastLen = lastVector.length();
966 SkVector nextV = vector;
967 SkScalar nextLen = vector.length();
968 if (lastLen < nextLen) {
969 lastV.setLength(nextLen);
970 } else {
971 nextV.setLength(lastLen);
972 }
973
974 SkVector bisect = { (lastV.fX + nextV.fX) / 2, (lastV.fY + nextV.fY) / 2 };
975 bisect.setLength(fWidthControl.fValLo * 2);
976 if (fBisectButton.enabled()) {
Hal Canary23e474c2017-05-15 13:35:35 -0400977 canvas->drawLine(pt, pt + bisect, fSkeletonPaint);
caryclark64022c12016-05-27 05:13:26 -0700978 }
979 lastV.setLength(fWidthControl.fValLo);
980 if (fBisectButton.enabled()) {
Hal Canary23e474c2017-05-15 13:35:35 -0400981 canvas->drawLine(pt, {pt.fX - lastV.fY, pt.fY + lastV.fX}, fSkeletonPaint);
caryclark64022c12016-05-27 05:13:26 -0700982 }
983 nextV.setLength(fWidthControl.fValLo);
984 if (fBisectButton.enabled()) {
Hal Canary23e474c2017-05-15 13:35:35 -0400985 canvas->drawLine(pt, {pt.fX + nextV.fY, pt.fY - nextV.fX}, fSkeletonPaint);
caryclark64022c12016-05-27 05:13:26 -0700986 }
987 if (fJoinButton.enabled()) {
988 SkScalar r = fWidthControl.fValLo;
989 SkRect oval = { pt.fX - r, pt.fY - r, pt.fX + r, pt.fY + r};
990 SkScalar startAngle = SkScalarATan2(lastV.fX, -lastV.fY) * 180.f / SK_ScalarPI;
991 SkScalar endAngle = SkScalarATan2(-nextV.fX, nextV.fY) * 180.f / SK_ScalarPI;
992 if (endAngle > startAngle) {
993 canvas->drawArc(oval, startAngle, endAngle - startAngle, false, fSkeletonPaint);
994 } else {
995 canvas->drawArc(oval, startAngle, 360 - (startAngle - endAngle), false,
996 fSkeletonPaint);
997 }
998 }
999 }
1000
1001 void draw_bisects(SkCanvas* canvas, bool activeOnly) {
1002 SkVector firstVector, lastVector, nextLast, vector;
1003 SkPoint pts[4];
1004 SkPoint firstPt = { 0, 0 }; // init to avoid warning;
1005 SkPath::Verb verb;
1006 SkPath::Iter iter(fPath, true);
1007 bool foundFirst = false;
1008 int counter = -1;
1009 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1010 ++counter;
1011 if (activeOnly && counter != fActiveVerb && counter - 1 != fActiveVerb
1012 && counter + 1 != fActiveVerb
1013 && (fActiveVerb != 1 || counter != fPath.countVerbs())) {
1014 continue;
1015 }
1016 switch (verb) {
1017 case SkPath::kLine_Verb:
1018 nextLast = pts[0] - pts[1];
1019 vector = pts[1] - pts[0];
1020 break;
1021 case SkPath::kQuad_Verb: {
1022 nextLast = pts[1] - pts[2];
1023 if (SkScalarNearlyZero(nextLast.length())) {
1024 nextLast = pts[0] - pts[2];
1025 }
1026 vector = pts[1] - pts[0];
1027 if (SkScalarNearlyZero(vector.length())) {
1028 vector = pts[2] - pts[0];
1029 }
1030 if (!fBisectButton.enabled()) {
1031 break;
1032 }
1033 SkScalar t = SkFindQuadMaxCurvature(pts);
1034 if (0 < t && t < 1) {
1035 SkPoint maxPt = SkEvalQuadAt(pts, t);
1036 SkVector tangent = SkEvalQuadTangentAt(pts, t);
1037 tangent.setLength(fWidthControl.fValLo * 2);
Hal Canary23e474c2017-05-15 13:35:35 -04001038 canvas->drawLine(maxPt, {maxPt.fX + tangent.fY, maxPt.fY - tangent.fX},
1039 fSkeletonPaint);
caryclark64022c12016-05-27 05:13:26 -07001040 }
1041 } break;
1042 case SkPath::kConic_Verb:
1043 nextLast = pts[1] - pts[2];
1044 if (SkScalarNearlyZero(nextLast.length())) {
1045 nextLast = pts[0] - pts[2];
1046 }
1047 vector = pts[1] - pts[0];
1048 if (SkScalarNearlyZero(vector.length())) {
1049 vector = pts[2] - pts[0];
1050 }
1051 if (!fBisectButton.enabled()) {
1052 break;
1053 }
1054 // FIXME : need max curvature or equivalent here
1055 break;
1056 case SkPath::kCubic_Verb: {
1057 nextLast = pts[2] - pts[3];
1058 if (SkScalarNearlyZero(nextLast.length())) {
1059 nextLast = pts[1] - pts[3];
1060 if (SkScalarNearlyZero(nextLast.length())) {
1061 nextLast = pts[0] - pts[3];
1062 }
1063 }
1064 vector = pts[0] - pts[1];
1065 if (SkScalarNearlyZero(vector.length())) {
1066 vector = pts[0] - pts[2];
1067 if (SkScalarNearlyZero(vector.length())) {
1068 vector = pts[0] - pts[3];
1069 }
1070 }
1071 if (!fBisectButton.enabled()) {
1072 break;
1073 }
1074 SkScalar tMax[2];
1075 int tMaxCount = SkFindCubicMaxCurvature(pts, tMax);
1076 for (int tIndex = 0; tIndex < tMaxCount; ++tIndex) {
1077 if (0 >= tMax[tIndex] || tMax[tIndex] >= 1) {
1078 continue;
1079 }
1080 SkPoint maxPt;
1081 SkVector tangent;
Ben Wagnera93a14a2017-08-28 10:34:05 -04001082 SkEvalCubicAt(pts, tMax[tIndex], &maxPt, &tangent, nullptr);
caryclark64022c12016-05-27 05:13:26 -07001083 tangent.setLength(fWidthControl.fValLo * 2);
Hal Canary23e474c2017-05-15 13:35:35 -04001084 canvas->drawLine(maxPt, {maxPt.fX + tangent.fY, maxPt.fY - tangent.fX},
1085 fSkeletonPaint);
caryclark64022c12016-05-27 05:13:26 -07001086 }
1087 } break;
1088 case SkPath::kClose_Verb:
1089 if (foundFirst) {
1090 draw_bisect(canvas, lastVector, firstVector, firstPt);
1091 foundFirst = false;
1092 }
1093 break;
Ben Wagner63fd7602017-10-09 15:45:33 -04001094 default:
caryclark64022c12016-05-27 05:13:26 -07001095 break;
1096 }
1097 if (SkPath::kLine_Verb <= verb && verb <= SkPath::kCubic_Verb) {
1098 if (!foundFirst) {
1099 firstPt = pts[0];
1100 firstVector = vector;
1101 foundFirst = true;
1102 } else {
1103 draw_bisect(canvas, lastVector, vector, pts[0]);
1104 }
1105 lastVector = nextLast;
1106 }
1107 }
1108 }
1109
1110 void draw_legend(SkCanvas* canvas);
1111
1112 void draw_segment(SkCanvas* canvas) {
1113 SkPoint pts[4];
1114 SkPath::Verb verb;
1115 SkPath::Iter iter(fPath, true);
1116 int counter = -1;
1117 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1118 if (++counter < fActiveVerb) {
1119 continue;
1120 }
1121 switch (verb) {
1122 case SkPath::kLine_Verb:
1123 canvas->drawPoints(SkCanvas::kLines_PointMode, 2, pts, fActivePaint);
1124 draw_points(canvas, pts, 2);
1125 break;
1126 case SkPath::kQuad_Verb: {
1127 SkPath qPath;
1128 qPath.moveTo(pts[0]);
1129 qPath.quadTo(pts[1], pts[2]);
1130 canvas->drawPath(qPath, fActivePaint);
1131 draw_points(canvas, pts, 3);
1132 } break;
1133 case SkPath::kConic_Verb: {
1134 SkPath conicPath;
1135 conicPath.moveTo(pts[0]);
1136 conicPath.conicTo(pts[1], pts[2], iter.conicWeight());
1137 canvas->drawPath(conicPath, fActivePaint);
1138 draw_points(canvas, pts, 3);
1139 } break;
1140 case SkPath::kCubic_Verb: {
Cary Clark7eb01e02016-12-08 14:36:32 -05001141 SkScalar loopT[3];
1142 int complex = SkDCubic::ComplexBreak(pts, loopT);
caryclark64022c12016-05-27 05:13:26 -07001143 SkPath cPath;
1144 cPath.moveTo(pts[0]);
1145 cPath.cubicTo(pts[1], pts[2], pts[3]);
1146 canvas->drawPath(cPath, complex ? fComplexPaint : fActivePaint);
1147 draw_points(canvas, pts, 4);
1148 } break;
Ben Wagner63fd7602017-10-09 15:45:33 -04001149 default:
caryclark64022c12016-05-27 05:13:26 -07001150 break;
1151 }
1152 return;
1153 }
1154 }
1155
1156 void draw_points(SkCanvas* canvas, SkPoint* points, int count) {
1157 for (int index = 0; index < count; ++index) {
1158 canvas->drawCircle(points[index].fX, points[index].fY, 10, fPointPaint);
1159 }
1160 }
1161
1162 int hittest_verb(SkPoint pt, SkPath::Verb* verbPtr, SkScalar* weight) {
1163 SkIntersections i;
1164 SkDLine hHit = {{{pt.fX - kHitToleranace, pt.fY }, {pt.fX + kHitToleranace, pt.fY}}};
1165 SkDLine vHit = {{{pt.fX, pt.fY - kHitToleranace }, {pt.fX, pt.fY + kHitToleranace}}};
1166 SkPoint pts[4];
1167 SkPath::Verb verb;
1168 SkPath::Iter iter(fPath, true);
1169 int counter = -1;
1170 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1171 ++counter;
1172 switch (verb) {
1173 case SkPath::kLine_Verb: {
1174 SkDLine line;
1175 line.set(pts);
1176 if (i.intersect(line, hHit) || i.intersect(line, vHit)) {
1177 *verbPtr = verb;
1178 *weight = 1;
1179 return counter;
1180 }
1181 } break;
1182 case SkPath::kQuad_Verb: {
1183 SkDQuad quad;
1184 quad.set(pts);
1185 if (i.intersect(quad, hHit) || i.intersect(quad, vHit)) {
1186 *verbPtr = verb;
1187 *weight = 1;
1188 return counter;
1189 }
1190 } break;
1191 case SkPath::kConic_Verb: {
1192 SkDConic conic;
1193 SkScalar w = iter.conicWeight();
1194 conic.set(pts, w);
1195 if (i.intersect(conic, hHit) || i.intersect(conic, vHit)) {
1196 *verbPtr = verb;
1197 *weight = w;
1198 return counter;
1199 }
1200 } break;
1201 case SkPath::kCubic_Verb: {
1202 SkDCubic cubic;
1203 cubic.set(pts);
1204 if (i.intersect(cubic, hHit) || i.intersect(cubic, vHit)) {
1205 *verbPtr = verb;
1206 *weight = 1;
1207 return counter;
1208 }
1209 } break;
Ben Wagner63fd7602017-10-09 15:45:33 -04001210 default:
caryclark64022c12016-05-27 05:13:26 -07001211 break;
1212 }
1213 }
1214 return -1;
1215 }
1216
1217 SkScalar pt_to_line(SkPoint s, SkPoint e, int x, int y) {
1218 SkScalar radius = fWidthControl.fValLo;
1219 SkVector adjOpp = e - s;
Cary Clarkdf429f32017-11-08 11:44:31 -05001220 SkScalar lenSq = SkPointPriv::LengthSqd(adjOpp);
caryclark64022c12016-05-27 05:13:26 -07001221 SkPoint rotated = {
1222 (y - s.fY) * adjOpp.fY + (x - s.fX) * adjOpp.fX,
1223 (y - s.fY) * adjOpp.fX - (x - s.fX) * adjOpp.fY,
1224 };
1225 if (rotated.fX < 0 || rotated.fX > lenSq) {
1226 return -radius;
1227 }
1228 rotated.fY /= SkScalarSqrt(lenSq);
Brian Osman788b9162020-02-07 10:36:46 -05001229 return std::max(-radius, std::min(radius, rotated.fY));
caryclark64022c12016-05-27 05:13:26 -07001230 }
1231
1232 // given a line, compute the interior and exterior gradient coverage
1233 bool coverage(SkPoint s, SkPoint e, uint8_t* distanceMap, int w, int h) {
1234 SkScalar radius = fWidthControl.fValLo;
Brian Osman788b9162020-02-07 10:36:46 -05001235 int minX = std::max(0, (int) (std::min(s.fX, e.fX) - radius));
1236 int minY = std::max(0, (int) (std::min(s.fY, e.fY) - radius));
1237 int maxX = std::min(w, (int) (std::max(s.fX, e.fX) + radius) + 1);
1238 int maxY = std::min(h, (int) (std::max(s.fY, e.fY) + radius) + 1);
caryclark64022c12016-05-27 05:13:26 -07001239 for (int y = minY; y < maxY; ++y) {
1240 for (int x = minX; x < maxX; ++x) {
1241 SkScalar ptToLineDist = pt_to_line(s, e, x, y);
1242 if (ptToLineDist > -radius && ptToLineDist < radius) {
1243 SkScalar coverage = ptToLineDist / radius;
1244 add_to_map(1 - SkScalarAbs(coverage), x, y, distanceMap, w, h);
1245 }
1246 SkVector ptToS = { x - s.fX, y - s.fY };
1247 SkScalar dist = ptToS.length();
1248 if (dist < radius) {
1249 SkScalar coverage = dist / radius;
1250 add_to_map(1 - SkScalarAbs(coverage), x, y, distanceMap, w, h);
1251 }
1252 SkVector ptToE = { x - e.fX, y - e.fY };
1253 dist = ptToE.length();
1254 if (dist < radius) {
1255 SkScalar coverage = dist / radius;
1256 add_to_map(1 - SkScalarAbs(coverage), x, y, distanceMap, w, h);
1257 }
1258 }
1259 }
1260 return true;
1261 }
1262
1263 void quad_coverage(SkPoint pts[3], uint8_t* distanceMap, int w, int h) {
1264 SkScalar dist = pts[0].Distance(pts[0], pts[2]);
1265 if (dist < gCurveDistance) {
1266 (void) coverage(pts[0], pts[2], distanceMap, w, h);
1267 return;
1268 }
1269 SkPoint split[5];
1270 SkChopQuadAt(pts, split, 0.5f);
1271 quad_coverage(&split[0], distanceMap, w, h);
1272 quad_coverage(&split[2], distanceMap, w, h);
1273 }
1274
1275 void conic_coverage(SkPoint pts[3], SkScalar weight, uint8_t* distanceMap, int w, int h) {
1276 SkScalar dist = pts[0].Distance(pts[0], pts[2]);
1277 if (dist < gCurveDistance) {
1278 (void) coverage(pts[0], pts[2], distanceMap, w, h);
1279 return;
1280 }
1281 SkConic split[2];
1282 SkConic conic;
1283 conic.set(pts, weight);
caryclark414c4292016-09-26 11:03:54 -07001284 if (conic.chopAt(0.5f, split)) {
1285 conic_coverage(split[0].fPts, split[0].fW, distanceMap, w, h);
1286 conic_coverage(split[1].fPts, split[1].fW, distanceMap, w, h);
1287 }
caryclark64022c12016-05-27 05:13:26 -07001288 }
1289
1290 void cubic_coverage(SkPoint pts[4], uint8_t* distanceMap, int w, int h) {
1291 SkScalar dist = pts[0].Distance(pts[0], pts[3]);
1292 if (dist < gCurveDistance) {
1293 (void) coverage(pts[0], pts[3], distanceMap, w, h);
1294 return;
1295 }
1296 SkPoint split[7];
1297 SkChopCubicAt(pts, split, 0.5f);
1298 cubic_coverage(&split[0], distanceMap, w, h);
1299 cubic_coverage(&split[3], distanceMap, w, h);
1300 }
1301
1302 void path_coverage(const SkPath& path, uint8_t* distanceMap, int w, int h) {
1303 memset(distanceMap, 0, sizeof(distanceMap[0]) * w * h);
1304 SkPoint pts[4];
1305 SkPath::Verb verb;
1306 SkPath::Iter iter(path, true);
1307 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1308 switch (verb) {
1309 case SkPath::kLine_Verb:
1310 (void) coverage(pts[0], pts[1], distanceMap, w, h);
1311 break;
1312 case SkPath::kQuad_Verb:
1313 quad_coverage(pts, distanceMap, w, h);
1314 break;
1315 case SkPath::kConic_Verb:
1316 conic_coverage(pts, iter.conicWeight(), distanceMap, w, h);
1317 break;
1318 case SkPath::kCubic_Verb:
1319 cubic_coverage(pts, distanceMap, w, h);
1320 break;
Ben Wagner63fd7602017-10-09 15:45:33 -04001321 default:
caryclark64022c12016-05-27 05:13:26 -07001322 break;
1323 }
1324 }
1325 }
1326
1327 static uint8_t* set_up_dist_map(const SkImageInfo& imageInfo, SkBitmap* distMap) {
1328 distMap->setInfo(imageInfo);
1329 distMap->setIsVolatile(true);
1330 SkAssertResult(distMap->tryAllocPixels());
1331 SkASSERT((int) distMap->rowBytes() == imageInfo.width());
1332 return distMap->getAddr8(0, 0);
1333 }
1334
1335 void path_stroke(int index, SkPath* inner, SkPath* outer) {
1336 #if 0
1337 SkPathStroker stroker(fPath, fWidthControl.fValLo, 0,
1338 SkPaint::kRound_Cap, SkPaint::kRound_Join, fResControl.fValLo);
1339 SkPoint pts[4], firstPt, lastPt;
1340 SkPath::Verb verb;
1341 SkPath::Iter iter(fPath, true);
1342 int counter = -1;
1343 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1344 ++counter;
1345 switch (verb) {
1346 case SkPath::kMove_Verb:
1347 firstPt = pts[0];
1348 break;
1349 case SkPath::kLine_Verb:
1350 if (counter == index) {
1351 stroker.moveTo(pts[0]);
1352 stroker.lineTo(pts[1]);
1353 goto done;
1354 }
1355 lastPt = pts[1];
1356 break;
1357 case SkPath::kQuad_Verb:
1358 if (counter == index) {
1359 stroker.moveTo(pts[0]);
1360 stroker.quadTo(pts[1], pts[2]);
1361 goto done;
1362 }
1363 lastPt = pts[2];
1364 break;
1365 case SkPath::kConic_Verb:
1366 if (counter == index) {
1367 stroker.moveTo(pts[0]);
1368 stroker.conicTo(pts[1], pts[2], iter.conicWeight());
1369 goto done;
1370 }
1371 lastPt = pts[2];
1372 break;
1373 case SkPath::kCubic_Verb:
1374 if (counter == index) {
1375 stroker.moveTo(pts[0]);
1376 stroker.cubicTo(pts[1], pts[2], pts[3]);
1377 goto done;
1378 }
1379 lastPt = pts[3];
1380 break;
1381 case SkPath::kClose_Verb:
1382 if (counter == index) {
1383 stroker.moveTo(lastPt);
1384 stroker.lineTo(firstPt);
1385 goto done;
1386 }
1387 break;
1388 case SkPath::kDone_Verb:
1389 break;
1390 default:
1391 SkASSERT(0);
1392 }
1393 }
1394 done:
1395 *inner = stroker.fInner;
1396 *outer = stroker.fOuter;
1397#endif
1398 }
1399
1400 void draw_stroke(SkCanvas* canvas, int active) {
1401 SkPath inner, outer;
1402 path_stroke(active, &inner, &outer);
1403 canvas->drawPath(inner, fSkeletonPaint);
1404 canvas->drawPath(outer, fSkeletonPaint);
1405 }
1406
1407 void gather_strokes() {
1408 fStrokes.reset();
1409 for (int index = 0; index < fPath.countVerbs(); ++index) {
1410 Stroke& inner = fStrokes.push_back();
1411 inner.reset();
1412 inner.fInner = true;
1413 Stroke& outer = fStrokes.push_back();
1414 outer.reset();
1415 outer.fInner = false;
1416 path_stroke(index, &inner.fPath, &outer.fPath);
1417 }
1418 }
1419
1420 void trim_strokes() {
1421 // eliminate self-itersecting loops
1422 // trim outside edges
1423 gather_strokes();
1424 for (int index = 0; index < fStrokes.count(); ++index) {
1425 SkPath& outPath = fStrokes[index].fPath;
1426 for (int inner = 0; inner < fStrokes.count(); ++inner) {
1427 if (index == inner) {
1428 continue;
1429 }
1430 SkPath& inPath = fStrokes[inner].fPath;
1431 if (!outPath.getBounds().intersects(inPath.getBounds())) {
1432 continue;
1433 }
Ben Wagner63fd7602017-10-09 15:45:33 -04001434
caryclark64022c12016-05-27 05:13:26 -07001435 }
1436 }
1437 }
1438
1439 void onDrawContent(SkCanvas* canvas) override {
1440#if 0
1441 SkDEBUGCODE(SkDebugStrokeGlobals debugGlobals);
1442 SkOpAA aaResult(fPath, fWidthControl.fValLo, fResControl.fValLo
1443 SkDEBUGPARAMS(&debugGlobals));
1444#endif
1445 SkPath strokePath;
1446// aaResult.simplify(&strokePath);
1447 canvas->drawPath(strokePath, fSkeletonPaint);
1448 SkRect bounds = fPath.getBounds();
1449 SkScalar radius = fWidthControl.fValLo;
1450 int w = (int) (bounds.fRight + radius + 1);
1451 int h = (int) (bounds.fBottom + radius + 1);
1452 SkImageInfo imageInfo = SkImageInfo::MakeA8(w, h);
1453 SkBitmap distMap;
1454 uint8_t* distanceMap = set_up_dist_map(imageInfo, &distMap);
1455 path_coverage(fPath, distanceMap, w, h);
1456 if (fFillButton.enabled()) {
1457 canvas->drawPath(fPath, fCoveragePaint);
1458 }
1459 if (fFilterButton.fState == 2
1460 && (0 < fFilterControl.fValLo || fFilterControl.fValHi < 255)) {
1461 SkBitmap filteredMap;
1462 uint8_t* filtered = set_up_dist_map(imageInfo, &filteredMap);
1463 filter_coverage(distanceMap, sizeof(uint8_t) * w * h, (uint8_t) fFilterControl.fValLo,
1464 (uint8_t) fFilterControl.fValHi, filtered);
1465 canvas->drawBitmap(filteredMap, 0, 0, &fCoveragePaint);
1466 } else if (fFilterButton.enabled()) {
1467 canvas->drawBitmap(distMap, 0, 0, &fCoveragePaint);
1468 }
1469 if (fSkeletonButton.enabled()) {
1470 canvas->drawPath(fPath, fActiveVerb >= 0 ? fLightSkeletonPaint : fSkeletonPaint);
1471 }
1472 if (fActiveVerb >= 0) {
1473 draw_segment(canvas);
1474 }
1475 if (fBisectButton.enabled() || fJoinButton.enabled()) {
1476 draw_bisects(canvas, fActiveVerb >= 0);
1477 }
1478 if (fInOutButton.enabled()) {
1479 if (fActiveVerb >= 0) {
1480 draw_stroke(canvas, fActiveVerb);
1481 } else {
1482 for (int index = 0; index < fPath.countVerbs(); ++index) {
1483 draw_stroke(canvas, index);
1484 }
1485 }
1486 }
1487 if (fHideAll) {
1488 return;
1489 }
1490 for (int index = 0; index < kControlCount; ++index) {
1491 kControlList[index].fControl->draw(canvas, fControlPaints);
1492 }
1493 for (int index = 0; index < kButtonCount; ++index) {
1494 kButtonList[index].fButton->draw(canvas, fButtonPaints);
1495 }
1496 if (fShowLegend) {
1497 draw_legend(canvas);
1498 }
1499
1500#if 0
1501 SkPaint paint;
1502 paint.setARGB(255, 34, 31, 31);
1503 paint.setAntiAlias(true);
1504
1505 SkPath path;
1506 path.moveTo(18,439);
1507 path.lineTo(414,439);
1508 path.lineTo(414,702);
1509 path.lineTo(18,702);
1510 path.lineTo(18,439);
1511
1512 path.moveTo(19,701);
1513 path.lineTo(413,701);
1514 path.lineTo(413,440);
1515 path.lineTo(19,440);
1516 path.lineTo(19,701);
1517 path.close();
1518 canvas->drawPath(path, paint);
1519
1520 canvas->scale(1.0f, -1.0f);
1521 canvas->translate(0.0f, -800.0f);
1522 canvas->drawPath(path, paint);
1523#endif
1524
1525 }
1526
1527 int hittest_pt(SkPoint pt) {
1528 for (int index = 0; index < fPath.countPoints(); ++index) {
1529 if (SkPoint::Distance(fPath.getPoint(index), pt) <= kHitToleranace * 2) {
1530 return index;
1531 }
1532 }
1533 return -1;
1534 }
1535
Hal Canaryb1f411a2019-08-29 10:39:22 -04001536 virtual Sample::Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey modi) override {
caryclark64022c12016-05-27 05:13:26 -07001537 SkPoint pt = {x, y};
1538 int ptHit = hittest_pt(pt);
1539 if (ptHit >= 0) {
Hal Canaryfcf63592019-07-12 11:32:43 -04001540 return new MyClick(MyClick::kPtType, ptHit);
caryclark64022c12016-05-27 05:13:26 -07001541 }
1542 SkPath::Verb verb;
1543 SkScalar weight;
1544 int verbHit = hittest_verb(pt, &verb, &weight);
1545 if (verbHit >= 0) {
Hal Canaryfcf63592019-07-12 11:32:43 -04001546 return new MyClick(MyClick::kVerbType, verbHit, verb, weight);
caryclark64022c12016-05-27 05:13:26 -07001547 }
1548 if (!fHideAll) {
1549 const SkRect& rectPt = SkRect::MakeXYWH(x, y, 1, 1);
1550 for (int index = 0; index < kControlCount; ++index) {
1551 if (kControlList[index].fControl->contains(rectPt)) {
Hal Canaryfcf63592019-07-12 11:32:43 -04001552 return new MyClick(MyClick::kControlType,
caryclark64022c12016-05-27 05:13:26 -07001553 kControlList[index].fControlType);
1554 }
1555 }
1556 for (int index = 0; index < kButtonCount; ++index) {
1557 if (kButtonList[index].fButton->contains(rectPt)) {
Hal Canaryfcf63592019-07-12 11:32:43 -04001558 return new MyClick(MyClick::kControlType, kButtonList[index].fButtonType);
caryclark64022c12016-05-27 05:13:26 -07001559 }
1560 }
1561 }
1562 fLineButton.fVisible = fQuadButton.fVisible = fConicButton.fVisible
1563 = fCubicButton.fVisible = fWeightControl.fVisible = fAddButton.fVisible
1564 = fDeleteButton.fVisible = false;
1565 fActiveVerb = -1;
1566 fActivePt = -1;
1567 if (fHandlePathMove) {
Hal Canaryfcf63592019-07-12 11:32:43 -04001568 return new MyClick(MyClick::kPathType, MyClick::kPathMove);
caryclark64022c12016-05-27 05:13:26 -07001569 }
Hal Canaryfcf63592019-07-12 11:32:43 -04001570 return nullptr;
caryclark64022c12016-05-27 05:13:26 -07001571 }
1572
1573 static SkScalar MapScreenYtoValue(int y, const UniControl& control) {
Brian Osman788b9162020-02-07 10:36:46 -05001574 return std::min(1.f, std::max(0.f,
caryclark64022c12016-05-27 05:13:26 -07001575 SkIntToScalar(y) - control.fBounds.fTop) / control.fBounds.height())
1576 * (control.fMax - control.fMin) + control.fMin;
1577 }
1578
1579 bool onClick(Click* click) override {
1580 MyClick* myClick = (MyClick*) click;
1581 switch (myClick->fType) {
1582 case MyClick::kPtType: {
1583 savePath(click->fState);
1584 fActivePt = myClick->ptHit();
1585 SkPoint pt = fPath.getPoint((int) myClick->fControl);
Hal Canaryfcf63592019-07-12 11:32:43 -04001586 pt.offset(SkIntToScalar(click->fCurr.fX - click->fPrev.fX),
1587 SkIntToScalar(click->fCurr.fY - click->fPrev.fY));
Chris Daltone7ee0692020-01-06 10:18:30 -07001588 ToolUtils::set_path_pt(fActivePt, pt, &fPath);
caryclark64022c12016-05-27 05:13:26 -07001589 validatePath();
caryclark64022c12016-05-27 05:13:26 -07001590 return true;
1591 }
1592 case MyClick::kPathType:
1593 savePath(click->fState);
Hal Canaryfcf63592019-07-12 11:32:43 -04001594 fPath.offset(SkIntToScalar(click->fCurr.fX - click->fPrev.fX),
1595 SkIntToScalar(click->fCurr.fY - click->fPrev.fY));
caryclark64022c12016-05-27 05:13:26 -07001596 validatePath();
caryclark64022c12016-05-27 05:13:26 -07001597 return true;
1598 case MyClick::kVerbType: {
1599 fActiveVerb = myClick->verbHit();
1600 fLineButton.fVisible = fQuadButton.fVisible = fConicButton.fVisible
1601 = fCubicButton.fVisible = fAddButton.fVisible = fDeleteButton.fVisible
1602 = true;
1603 fLineButton.setEnabled(myClick->fVerb == SkPath::kLine_Verb);
1604 fQuadButton.setEnabled(myClick->fVerb == SkPath::kQuad_Verb);
1605 fConicButton.setEnabled(myClick->fVerb == SkPath::kConic_Verb);
1606 fCubicButton.setEnabled(myClick->fVerb == SkPath::kCubic_Verb);
1607 fWeightControl.fValLo = myClick->fWeight;
1608 fWeightControl.fVisible = myClick->fVerb == SkPath::kConic_Verb;
1609 } break;
1610 case MyClick::kControlType: {
Hal Canaryb1f411a2019-08-29 10:39:22 -04001611 if (click->fState != skui::InputState::kDown && myClick->isButton()) {
caryclark64022c12016-05-27 05:13:26 -07001612 return true;
1613 }
1614 switch (myClick->fControl) {
1615 case MyClick::kFilterControl: {
Hal Canaryfcf63592019-07-12 11:32:43 -04001616 SkScalar val = MapScreenYtoValue(click->fCurr.fY, fFilterControl);
caryclark64022c12016-05-27 05:13:26 -07001617 if (val - fFilterControl.fValLo < fFilterControl.fValHi - val) {
Brian Osman788b9162020-02-07 10:36:46 -05001618 fFilterControl.fValLo = std::max(0.f, val);
caryclark64022c12016-05-27 05:13:26 -07001619 } else {
Brian Osman788b9162020-02-07 10:36:46 -05001620 fFilterControl.fValHi = std::min(255.f, val);
caryclark64022c12016-05-27 05:13:26 -07001621 }
1622 } break;
1623 case MyClick::kResControl:
Hal Canaryfcf63592019-07-12 11:32:43 -04001624 fResControl.fValLo = MapScreenYtoValue(click->fCurr.fY, fResControl);
caryclark64022c12016-05-27 05:13:26 -07001625 break;
1626 case MyClick::kWeightControl: {
1627 savePath(click->fState);
Hal Canaryfcf63592019-07-12 11:32:43 -04001628 SkScalar w = MapScreenYtoValue(click->fCurr.fY, fWeightControl);
caryclark64022c12016-05-27 05:13:26 -07001629 set_path_weight(fActiveVerb, w, &fPath);
1630 validatePath();
1631 fWeightControl.fValLo = w;
1632 } break;
1633 case MyClick::kWidthControl:
Hal Canaryfcf63592019-07-12 11:32:43 -04001634 fWidthControl.fValLo = MapScreenYtoValue(click->fCurr.fY, fWidthControl);
caryclark64022c12016-05-27 05:13:26 -07001635 break;
1636 case MyClick::kLineButton:
1637 savePath(click->fState);
1638 enable_verb_button(myClick->fControl);
1639 fWeightControl.fVisible = false;
1640 set_path_verb(fActiveVerb, SkPath::kLine_Verb, &fPath, 1);
1641 validatePath();
1642 break;
1643 case MyClick::kQuadButton:
1644 savePath(click->fState);
1645 enable_verb_button(myClick->fControl);
1646 fWeightControl.fVisible = false;
1647 set_path_verb(fActiveVerb, SkPath::kQuad_Verb, &fPath, 1);
1648 validatePath();
1649 break;
1650 case MyClick::kConicButton: {
1651 savePath(click->fState);
1652 enable_verb_button(myClick->fControl);
1653 fWeightControl.fVisible = true;
1654 const SkScalar defaultConicWeight = 1.f / SkScalarSqrt(2);
1655 set_path_verb(fActiveVerb, SkPath::kConic_Verb, &fPath, defaultConicWeight);
1656 validatePath();
1657 fWeightControl.fValLo = get_path_weight(fActiveVerb, fPath);
1658 } break;
1659 case MyClick::kCubicButton:
1660 savePath(click->fState);
1661 enable_verb_button(myClick->fControl);
1662 fWeightControl.fVisible = false;
1663 set_path_verb(fActiveVerb, SkPath::kCubic_Verb, &fPath, 1);
1664 validatePath();
1665 break;
1666 case MyClick::kAddButton:
1667 savePath(click->fState);
1668 add_path_segment(fActiveVerb, &fPath);
1669 validatePath();
1670 if (fWeightControl.fVisible) {
1671 fWeightControl.fValLo = get_path_weight(fActiveVerb, fPath);
1672 }
1673 break;
1674 case MyClick::kDeleteButton:
1675 savePath(click->fState);
1676 delete_path_segment(fActiveVerb, &fPath);
1677 validatePath();
1678 break;
1679 case MyClick::kFillButton:
1680 fFillButton.toggle();
1681 break;
1682 case MyClick::kSkeletonButton:
1683 fSkeletonButton.toggle();
1684 break;
1685 case MyClick::kFilterButton:
1686 fFilterButton.toggle();
1687 fFilterControl.fVisible = fFilterButton.fState == 2;
1688 break;
1689 case MyClick::kBisectButton:
1690 fBisectButton.toggle();
1691 break;
1692 case MyClick::kJoinButton:
1693 fJoinButton.toggle();
1694 break;
1695 case MyClick::kInOutButton:
1696 fInOutButton.toggle();
1697 break;
1698 default:
1699 SkASSERT(0);
1700 break;
1701 }
1702 } break;
1703 default:
1704 SkASSERT(0);
1705 break;
1706 }
1707 setControlButtonsPos();
caryclark64022c12016-05-27 05:13:26 -07001708 return true;
1709 }
1710
1711private:
Ben Wagnerb2c4ea62018-08-08 11:36:17 -04001712 typedef Sample INHERITED;
caryclark64022c12016-05-27 05:13:26 -07001713};
1714
1715static struct KeyCommand {
1716 char fKey;
1717 char fAlternate;
1718 const char* fDescriptionL;
1719 const char* fDescriptionR;
1720 bool (AAGeometryView::*fFunction)();
1721} kKeyCommandList[] = {
Ben Wagner63fd7602017-10-09 15:45:33 -04001722 { ' ', 0, "space", "center path", &AAGeometryView::scaleToFit },
1723 { '-', 0, "-", "zoom out", &AAGeometryView::scaleDown },
1724 { '+', '=', "+/=", "zoom in", &AAGeometryView::scaleUp },
Chris Dalton08d1a252017-10-20 11:46:47 -06001725 { 'D', 0, "D", "dump to console", &AAGeometryView::pathDump },
1726 { 'H', 0, "H", "hide controls", &AAGeometryView::hideAll },
1727 { 'R', 0, "R", "reset path", &AAGeometryView::constructPath },
1728 { 'Z', 0, "Z", "undo", &AAGeometryView::undo },
caryclark64022c12016-05-27 05:13:26 -07001729 { '?', 0, "?", "show legend", &AAGeometryView::showLegend },
1730};
1731
1732const int kKeyCommandCount = (int) SK_ARRAY_COUNT(kKeyCommandList);
1733
1734void AAGeometryView::draw_legend(SkCanvas* canvas) {
1735 SkScalar bottomOffset = this->height() - 10;
1736 for (int index = kKeyCommandCount - 1; index >= 0; --index) {
1737 bottomOffset -= 15;
Mike Reedb579f072019-01-03 15:45:53 -05001738 SkTextUtils::DrawString(canvas, kKeyCommandList[index].fDescriptionL, this->width() - 160, bottomOffset,
1739 fLegendLeftFont, SkPaint());
Mike Reeda697df92018-10-26 07:28:30 -04001740 SkTextUtils::DrawString(canvas, kKeyCommandList[index].fDescriptionR,
Cary Clark2a475ea2017-04-28 15:35:12 -04001741 this->width() - 20, bottomOffset,
Mike Reedb579f072019-01-03 15:45:53 -05001742 fLegendRightFont, SkPaint(), SkTextUtils::kRight_Align);
caryclark64022c12016-05-27 05:13:26 -07001743 }
1744}
1745
Hal Canary6cc65e12019-07-03 15:53:04 -04001746bool AAGeometryView::onChar(SkUnichar uni) {
caryclark64022c12016-05-27 05:13:26 -07001747 for (int index = 0; index < kButtonCount; ++index) {
1748 Button* button = kButtonList[index].fButton;
1749 if (button->fVisible && uni == button->fLabel) {
Hal Canaryfcf63592019-07-12 11:32:43 -04001750 MyClick click(MyClick::kControlType, kButtonList[index].fButtonType);
Hal Canaryb1f411a2019-08-29 10:39:22 -04001751 click.fState = skui::InputState::kDown;
caryclark64022c12016-05-27 05:13:26 -07001752 (void) this->onClick(&click);
1753 return true;
1754 }
1755 }
1756 for (int index = 0; index < kKeyCommandCount; ++index) {
1757 KeyCommand& keyCommand = kKeyCommandList[index];
1758 if (uni == keyCommand.fKey || uni == keyCommand.fAlternate) {
1759 return (this->*keyCommand.fFunction)();
1760 }
1761 }
1762 if (('A' <= uni && uni <= 'Z') || ('a' <= uni && uni <= 'z')) {
1763 for (int index = 0; index < kButtonCount; ++index) {
1764 Button* button = kButtonList[index].fButton;
1765 if (button->fVisible && (uni & ~0x20) == (button->fLabel & ~0x20)) {
Hal Canaryfcf63592019-07-12 11:32:43 -04001766 MyClick click(MyClick::kControlType, kButtonList[index].fButtonType);
Hal Canaryb1f411a2019-08-29 10:39:22 -04001767 click.fState = skui::InputState::kDown;
caryclark64022c12016-05-27 05:13:26 -07001768 (void) this->onClick(&click);
1769 return true;
1770 }
1771 }
1772 }
Hal Canary6cc65e12019-07-03 15:53:04 -04001773 return false;
caryclark64022c12016-05-27 05:13:26 -07001774}
Ben Wagner63fd7602017-10-09 15:45:33 -04001775
caryclark64022c12016-05-27 05:13:26 -07001776DEF_SAMPLE( return new AAGeometryView; )