blob: 5f2e624bdc79ced00ac9f915c6d8fa4c99dcf637 [file] [log] [blame]
caryclark64022c12016-05-27 05:13:26 -07001/*
2 * Copyright 2015 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
Mike Kleinc0bd9f92019-04-23 12:05:21 -05008#include "include/core/SkBitmap.h"
9#include "include/core/SkCanvas.h"
Hal Canary8918d532019-07-12 10:04:14 -040010#include "include/core/SkString.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050011#include "include/private/SkMacros.h"
Hal Canary8918d532019-07-12 10:04:14 -040012#include "include/utils/SkTextUtils.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050013#include "samplecode/Sample.h"
14#include "src/core/SkGeometry.h"
Chris Dalton8d3eb242020-05-04 10:43:33 -060015#include "src/core/SkPathPriv.h"
Hal Canary8918d532019-07-12 10:04:14 -040016#include "src/core/SkPointPriv.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050017#include "src/pathops/SkIntersections.h"
18#include "src/pathops/SkOpEdgeBuilder.h"
Chris Daltone7ee0692020-01-06 10:18:30 -070019#include "tools/ToolUtils.h"
caryclark64022c12016-05-27 05:13:26 -070020
21#if 0
22void SkStrokeSegment::dump() const {
23 SkDebugf("{{{%1.9g,%1.9g}, {%1.9g,%1.9g}", fPts[0].fX, fPts[0].fY, fPts[1].fX, fPts[1].fY);
24 if (SkPath::kQuad_Verb == fVerb) {
25 SkDebugf(", {%1.9g,%1.9g}", fPts[2].fX, fPts[2].fY);
26 }
27 SkDebugf("}}");
28#ifdef SK_DEBUG
29 SkDebugf(" id=%d", fDebugID);
30#endif
31 SkDebugf("\n");
32}
33
34void SkStrokeSegment::dumpAll() const {
35 const SkStrokeSegment* segment = this;
36 while (segment) {
37 segment->dump();
38 segment = segment->fNext;
39 }
40}
41
42void SkStrokeTriple::dump() const {
43 SkDebugf("{{{%1.9g,%1.9g}, {%1.9g,%1.9g}", fPts[0].fX, fPts[0].fY, fPts[1].fX, fPts[1].fY);
44 if (SkPath::kQuad_Verb <= fVerb) {
45 SkDebugf(", {%1.9g,%1.9g}", fPts[2].fX, fPts[2].fY);
46 }
47 if (SkPath::kCubic_Verb == fVerb) {
48 SkDebugf(", {%1.9g,%1.9g}", fPts[3].fX, fPts[3].fY);
49 } else if (SkPath::kConic_Verb == fVerb) {
50 SkDebugf(", %1.9g", weight());
51 }
52 SkDebugf("}}");
53#ifdef SK_DEBUG
54 SkDebugf(" triple id=%d", fDebugID);
55#endif
56 SkDebugf("\ninner:\n");
57 fInner->dumpAll();
58 SkDebugf("outer:\n");
59 fOuter->dumpAll();
60 SkDebugf("join:\n");
61 fJoin->dumpAll();
62}
63
64void SkStrokeTriple::dumpAll() const {
65 const SkStrokeTriple* triple = this;
66 while (triple) {
67 triple->dump();
68 triple = triple->fNext;
69 }
70}
71
72void SkStrokeContour::dump() const {
73#ifdef SK_DEBUG
74 SkDebugf("id=%d ", fDebugID);
75#endif
76 SkDebugf("head:\n");
77 fHead->dumpAll();
78 SkDebugf("head cap:\n");
79 fHeadCap->dumpAll();
80 SkDebugf("tail cap:\n");
81 fTailCap->dumpAll();
82}
83
84void SkStrokeContour::dumpAll() const {
85 const SkStrokeContour* contour = this;
86 while (contour) {
87 contour->dump();
88 contour = contour->fNext;
89 }
90}
91#endif
92
93SkScalar gCurveDistance = 10;
94
95#if 0 // unused
96static SkPath::Verb get_path_verb(int index, const SkPath& path) {
97 if (index < 0) {
98 return SkPath::kMove_Verb;
99 }
100 SkPoint pts[4];
101 SkPath::Verb verb;
102 SkPath::Iter iter(path, true);
103 int counter = -1;
104 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
105 if (++counter < index) {
106 continue;
107 }
108 return verb;
109 }
110 SkASSERT(0);
111 return SkPath::kMove_Verb;
112}
113#endif
114
115static SkScalar get_path_weight(int index, const SkPath& path) {
116 SkPoint pts[4];
117 SkPath::Verb verb;
118 SkPath::Iter iter(path, true);
119 int counter = -1;
120 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
121 if (++counter < index) {
122 continue;
123 }
124 return verb == SkPath::kConic_Verb ? iter.conicWeight() : 1;
125 }
126 SkASSERT(0);
127 return 0;
128}
129
caryclark64022c12016-05-27 05:13:26 -0700130static void add_path_segment(int index, SkPath* path) {
131 SkPath result;
132 SkPoint pts[4];
133 SkPoint firstPt = { 0, 0 }; // init to avoid warning
134 SkPoint lastPt = { 0, 0 }; // init to avoid warning
135 SkPath::Verb verb;
136 SkPath::RawIter iter(*path);
137 int counter = -1;
138 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
139 SkScalar weight SK_INIT_TO_AVOID_WARNING;
140 if (++counter == index) {
141 switch (verb) {
142 case SkPath::kLine_Verb:
143 result.lineTo((pts[0].fX + pts[1].fX) / 2, (pts[0].fY + pts[1].fY) / 2);
144 break;
145 case SkPath::kQuad_Verb: {
146 SkPoint chop[5];
147 SkChopQuadAtHalf(pts, chop);
148 result.quadTo(chop[1], chop[2]);
149 pts[1] = chop[3];
150 } break;
151 case SkPath::kConic_Verb: {
152 SkConic chop[2];
153 SkConic conic;
154 conic.set(pts, iter.conicWeight());
caryclark414c4292016-09-26 11:03:54 -0700155 if (!conic.chopAt(0.5f, chop)) {
156 return;
157 }
caryclark64022c12016-05-27 05:13:26 -0700158 result.conicTo(chop[0].fPts[1], chop[0].fPts[2], chop[0].fW);
159 pts[1] = chop[1].fPts[1];
160 weight = chop[1].fW;
161 } break;
162 case SkPath::kCubic_Verb: {
163 SkPoint chop[7];
164 SkChopCubicAtHalf(pts, chop);
165 result.cubicTo(chop[1], chop[2], chop[3]);
166 pts[1] = chop[4];
167 pts[2] = chop[5];
168 } break;
169 case SkPath::kClose_Verb: {
170 result.lineTo((lastPt.fX + firstPt.fX) / 2, (lastPt.fY + firstPt.fY) / 2);
171 } break;
172 default:
173 SkASSERT(0);
174 }
175 } else if (verb == SkPath::kConic_Verb) {
176 weight = iter.conicWeight();
177 }
178 switch (verb) {
179 case SkPath::kMove_Verb:
180 result.moveTo(firstPt = pts[0]);
181 break;
182 case SkPath::kLine_Verb:
183 result.lineTo(lastPt = pts[1]);
184 break;
185 case SkPath::kQuad_Verb:
186 result.quadTo(pts[1], lastPt = pts[2]);
187 break;
188 case SkPath::kConic_Verb:
189 result.conicTo(pts[1], lastPt = pts[2], weight);
190 break;
191 case SkPath::kCubic_Verb:
192 result.cubicTo(pts[1], pts[2], lastPt = pts[3]);
193 break;
194 case SkPath::kClose_Verb:
195 result.close();
196 break;
197 case SkPath::kDone_Verb:
198 break;
199 default:
200 SkASSERT(0);
201 }
202 }
203 *path = result;
204}
205
206static void delete_path_segment(int index, SkPath* path) {
207 SkPath result;
208 SkPoint pts[4];
209 SkPath::Verb verb;
210 SkPath::RawIter iter(*path);
211 int counter = -1;
212 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
213 if (++counter == index) {
214 continue;
215 }
216 switch (verb) {
217 case SkPath::kMove_Verb:
218 result.moveTo(pts[0]);
219 break;
220 case SkPath::kLine_Verb:
221 result.lineTo(pts[1]);
222 break;
223 case SkPath::kQuad_Verb:
224 result.quadTo(pts[1], pts[2]);
225 break;
226 case SkPath::kConic_Verb:
227 result.conicTo(pts[1], pts[2], iter.conicWeight());
228 break;
229 case SkPath::kCubic_Verb:
230 result.cubicTo(pts[1], pts[2], pts[3]);
231 break;
232 case SkPath::kClose_Verb:
233 result.close();
234 break;
235 case SkPath::kDone_Verb:
236 break;
237 default:
238 SkASSERT(0);
239 }
240 }
241 *path = result;
242}
243
244static void set_path_weight(int index, SkScalar w, SkPath* path) {
245 SkPath result;
246 SkPoint pts[4];
247 SkPath::Verb verb;
248 SkPath::Iter iter(*path, true);
249 int counter = -1;
250 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
251 ++counter;
252 switch (verb) {
253 case SkPath::kMove_Verb:
254 result.moveTo(pts[0]);
255 break;
256 case SkPath::kLine_Verb:
257 result.lineTo(pts[1]);
258 break;
259 case SkPath::kQuad_Verb:
260 result.quadTo(pts[1], pts[2]);
261 break;
262 case SkPath::kConic_Verb:
263 result.conicTo(pts[1], pts[2], counter == index ? w : iter.conicWeight());
264 break;
265 case SkPath::kCubic_Verb:
266 result.cubicTo(pts[1], pts[2], pts[3]);
267 break;
268 case SkPath::kClose_Verb:
269 result.close();
270 break;
271 case SkPath::kDone_Verb:
272 break;
273 default:
274 SkASSERT(0);
275 }
276 }
277 *path = result;
278}
279
280static void set_path_verb(int index, SkPath::Verb v, SkPath* path, SkScalar w) {
281 SkASSERT(SkPath::kLine_Verb <= v && v <= SkPath::kCubic_Verb);
282 SkPath result;
283 SkPoint pts[4];
284 SkPath::Verb verb;
285 SkPath::Iter iter(*path, true);
286 int counter = -1;
287 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
288 SkScalar weight = verb == SkPath::kConic_Verb ? iter.conicWeight() : 1;
289 if (++counter == index && v != verb) {
290 SkASSERT(SkPath::kLine_Verb <= verb && verb <= SkPath::kCubic_Verb);
291 switch (verb) {
292 case SkPath::kLine_Verb:
293 switch (v) {
294 case SkPath::kConic_Verb:
295 weight = w;
296 case SkPath::kQuad_Verb:
297 pts[2] = pts[1];
298 pts[1].fX = (pts[0].fX + pts[2].fX) / 2;
299 pts[1].fY = (pts[0].fY + pts[2].fY) / 2;
300 break;
301 case SkPath::kCubic_Verb:
302 pts[3] = pts[1];
303 pts[1].fX = (pts[0].fX * 2 + pts[3].fX) / 3;
304 pts[1].fY = (pts[0].fY * 2 + pts[3].fY) / 3;
305 pts[2].fX = (pts[0].fX + pts[3].fX * 2) / 3;
306 pts[2].fY = (pts[0].fY + pts[3].fY * 2) / 3;
307 break;
308 default:
309 SkASSERT(0);
310 break;
311 }
312 break;
313 case SkPath::kQuad_Verb:
314 case SkPath::kConic_Verb:
315 switch (v) {
316 case SkPath::kLine_Verb:
317 pts[1] = pts[2];
318 break;
319 case SkPath::kConic_Verb:
320 weight = w;
321 case SkPath::kQuad_Verb:
322 break;
323 case SkPath::kCubic_Verb: {
324 SkDQuad dQuad;
325 dQuad.set(pts);
326 SkDCubic dCubic = dQuad.debugToCubic();
327 pts[3] = pts[2];
328 pts[1] = dCubic[1].asSkPoint();
329 pts[2] = dCubic[2].asSkPoint();
330 } break;
331 default:
332 SkASSERT(0);
333 break;
334 }
335 break;
336 case SkPath::kCubic_Verb:
337 switch (v) {
338 case SkPath::kLine_Verb:
339 pts[1] = pts[3];
340 break;
341 case SkPath::kConic_Verb:
342 weight = w;
343 case SkPath::kQuad_Verb: {
344 SkDCubic dCubic;
345 dCubic.set(pts);
346 SkDQuad dQuad = dCubic.toQuad();
347 pts[1] = dQuad[1].asSkPoint();
348 pts[2] = pts[3];
349 } break;
350 default:
351 SkASSERT(0);
352 break;
353 }
354 break;
355 default:
356 SkASSERT(0);
357 break;
358 }
359 verb = v;
360 }
361 switch (verb) {
362 case SkPath::kMove_Verb:
363 result.moveTo(pts[0]);
364 break;
365 case SkPath::kLine_Verb:
366 result.lineTo(pts[1]);
367 break;
368 case SkPath::kQuad_Verb:
369 result.quadTo(pts[1], pts[2]);
370 break;
371 case SkPath::kConic_Verb:
372 result.conicTo(pts[1], pts[2], weight);
373 break;
374 case SkPath::kCubic_Verb:
375 result.cubicTo(pts[1], pts[2], pts[3]);
376 break;
377 case SkPath::kClose_Verb:
378 result.close();
379 break;
380 default:
381 SkASSERT(0);
382 break;
383 }
384 }
385 *path = result;
386}
387
388static void add_to_map(SkScalar coverage, int x, int y, uint8_t* distanceMap, int w, int h) {
389 int byteCoverage = (int) (coverage * 256);
390 if (byteCoverage < 0) {
391 byteCoverage = 0;
392 } else if (byteCoverage > 255) {
393 byteCoverage = 255;
394 }
395 SkASSERT(x < w);
396 SkASSERT(y < h);
Brian Osman788b9162020-02-07 10:36:46 -0500397 distanceMap[y * w + x] = std::max(distanceMap[y * w + x], (uint8_t) byteCoverage);
caryclark64022c12016-05-27 05:13:26 -0700398}
399
400static void filter_coverage(const uint8_t* map, int len, uint8_t min, uint8_t max,
401 uint8_t* filter) {
402 for (int index = 0; index < len; ++index) {
403 uint8_t in = map[index];
404 filter[index] = in < min ? 0 : max < in ? 0 : in;
405 }
406}
407
408static void construct_path(SkPath& path) {
409 path.reset();
410 path.moveTo(442, 101.5f);
411 path.quadTo(413.5f, 691, 772, 514);
412 path.lineTo(346, 721.5f);
413 path.lineTo(154, 209);
414 path.lineTo(442, 101.5f);
415 path.close();
416}
417
418struct ButtonPaints {
419 static const int kMaxStateCount = 3;
420 SkPaint fDisabled;
421 SkPaint fStates[kMaxStateCount];
Mike Reedb579f072019-01-03 15:45:53 -0500422 SkFont fLabelFont;
caryclark64022c12016-05-27 05:13:26 -0700423
424 ButtonPaints() {
425 fStates[0].setAntiAlias(true);
426 fStates[0].setStyle(SkPaint::kStroke_Style);
427 fStates[0].setColor(0xFF3F0000);
428 fStates[1] = fStates[0];
429 fStates[1].setStrokeWidth(3);
430 fStates[2] = fStates[1];
431 fStates[2].setColor(0xFFcf0000);
Mike Reedb579f072019-01-03 15:45:53 -0500432 fLabelFont.setSize(25.0f);
caryclark64022c12016-05-27 05:13:26 -0700433 }
434};
435
436struct Button {
437 SkRect fBounds;
438 int fStateCount;
439 int fState;
440 char fLabel;
441 bool fVisible;
442
443 Button(char label) {
444 fStateCount = 2;
445 fState = 0;
446 fLabel = label;
447 fVisible = false;
448 }
449
450 Button(char label, int stateCount) {
451 SkASSERT(stateCount <= ButtonPaints::kMaxStateCount);
452 fStateCount = stateCount;
453 fState = 0;
454 fLabel = label;
455 fVisible = false;
456 }
457
458 bool contains(const SkRect& rect) {
459 return fVisible && fBounds.contains(rect);
460 }
461
462 bool enabled() {
463 return SkToBool(fState);
464 }
465
466 void draw(SkCanvas* canvas, const ButtonPaints& paints) {
467 if (!fVisible) {
468 return;
469 }
470 canvas->drawRect(fBounds, paints.fStates[fState]);
Ben Wagner51e15a62019-05-07 15:38:46 -0400471 SkTextUtils::Draw(canvas, &fLabel, 1, SkTextEncoding::kUTF8, fBounds.centerX(), fBounds.fBottom - 5,
Mike Reedb579f072019-01-03 15:45:53 -0500472 paints.fLabelFont, SkPaint(), SkTextUtils::kCenter_Align);
caryclark64022c12016-05-27 05:13:26 -0700473 }
474
475 void toggle() {
476 if (++fState == fStateCount) {
477 fState = 0;
478 }
479 }
480
481 void setEnabled(bool enabled) {
482 fState = (int) enabled;
483 }
484};
485
486struct ControlPaints {
487 SkPaint fOutline;
488 SkPaint fIndicator;
489 SkPaint fFill;
490 SkPaint fLabel;
491 SkPaint fValue;
492
Mike Reed89126e42019-01-03 12:59:14 -0500493 SkFont fLabelFont;
494 SkFont fValueFont;
495
caryclark64022c12016-05-27 05:13:26 -0700496 ControlPaints() {
497 fOutline.setAntiAlias(true);
498 fOutline.setStyle(SkPaint::kStroke_Style);
499 fIndicator = fOutline;
500 fIndicator.setColor(SK_ColorRED);
501 fFill.setAntiAlias(true);
502 fFill.setColor(0x7fff0000);
503 fLabel.setAntiAlias(true);
Mike Reed89126e42019-01-03 12:59:14 -0500504 fLabelFont.setSize(13.0f);
caryclark64022c12016-05-27 05:13:26 -0700505 fValue.setAntiAlias(true);
Mike Reed89126e42019-01-03 12:59:14 -0500506 fValueFont.setSize(11.0f);
caryclark64022c12016-05-27 05:13:26 -0700507 }
508};
509
510struct UniControl {
511 SkString fName;
512 SkRect fBounds;
513 SkScalar fMin;
514 SkScalar fMax;
515 SkScalar fValLo;
516 SkScalar fYLo;
517 bool fVisible;
518
519 UniControl(const char* name, SkScalar min, SkScalar max) {
520 fName = name;
521 fValLo = fMin = min;
522 fMax = max;
523 fVisible = false;
524
525 }
526
527 virtual ~UniControl() {}
528
529 bool contains(const SkRect& rect) {
530 return fVisible && fBounds.contains(rect);
531 }
532
533 virtual void draw(SkCanvas* canvas, const ControlPaints& paints) {
534 if (!fVisible) {
535 return;
536 }
537 canvas->drawRect(fBounds, paints.fOutline);
538 fYLo = fBounds.fTop + (fValLo - fMin) * fBounds.height() / (fMax - fMin);
539 canvas->drawLine(fBounds.fLeft - 5, fYLo, fBounds.fRight + 5, fYLo, paints.fIndicator);
540 SkString label;
541 label.printf("%0.3g", fValLo);
Hal Canary89a644b2019-01-07 09:36:09 -0500542 canvas->drawString(label, fBounds.fLeft + 5, fYLo - 5, paints.fValueFont, paints.fValue);
543 canvas->drawString(fName, fBounds.fLeft, fBounds.bottom() + 11, paints.fLabelFont,
544 paints.fLabel);
caryclark64022c12016-05-27 05:13:26 -0700545 }
546};
547
548struct BiControl : public UniControl {
549 SkScalar fValHi;
550
Ben Wagner63fd7602017-10-09 15:45:33 -0400551 BiControl(const char* name, SkScalar min, SkScalar max)
caryclark64022c12016-05-27 05:13:26 -0700552 : UniControl(name, min, max)
553 , fValHi(fMax) {
554 }
Ben Wagner63fd7602017-10-09 15:45:33 -0400555
caryclark64022c12016-05-27 05:13:26 -0700556 virtual ~BiControl() {}
557
558 virtual void draw(SkCanvas* canvas, const ControlPaints& paints) {
559 UniControl::draw(canvas, paints);
560 if (!fVisible || fValHi == fValLo) {
561 return;
562 }
563 SkScalar yPos = fBounds.fTop + (fValHi - fMin) * fBounds.height() / (fMax - fMin);
564 canvas->drawLine(fBounds.fLeft - 5, yPos, fBounds.fRight + 5, yPos, paints.fIndicator);
565 SkString label;
566 label.printf("%0.3g", fValHi);
567 if (yPos < fYLo + 10) {
568 yPos = fYLo + 10;
569 }
Hal Canary4484b8f2019-01-08 14:00:08 -0500570 canvas->drawString(label, fBounds.fLeft + 5, yPos - 5, paints.fValueFont, paints.fValue);
caryclark64022c12016-05-27 05:13:26 -0700571 SkRect fill = { fBounds.fLeft, fYLo, fBounds.fRight, yPos };
572 canvas->drawRect(fill, paints.fFill);
573 }
574};
575
576
Ben Wagnerb2c4ea62018-08-08 11:36:17 -0400577class MyClick : public Sample::Click {
caryclark64022c12016-05-27 05:13:26 -0700578public:
579 enum ClickType {
580 kInvalidType = -1,
581 kPtType,
582 kVerbType,
583 kControlType,
584 kPathType,
585 } fType;
586
587 enum ControlType {
588 kInvalidControl = -1,
589 kFirstControl,
590 kFilterControl = kFirstControl,
591 kResControl,
592 kWeightControl,
593 kWidthControl,
594 kLastControl = kWidthControl,
595 kFirstButton,
596 kCubicButton = kFirstButton,
597 kConicButton,
598 kQuadButton,
599 kLineButton,
600 kLastVerbButton = kLineButton,
601 kAddButton,
602 kDeleteButton,
603 kInOutButton,
604 kFillButton,
605 kSkeletonButton,
606 kFilterButton,
607 kBisectButton,
608 kJoinButton,
609 kLastButton = kJoinButton,
610 kPathMove,
611 } fControl;
612
613 SkPath::Verb fVerb;
614 SkScalar fWeight;
615
Hal Canaryfcf63592019-07-12 11:32:43 -0400616 MyClick(ClickType type, ControlType control)
617 : fType(type)
caryclark64022c12016-05-27 05:13:26 -0700618 , fControl(control)
619 , fVerb((SkPath::Verb) -1)
620 , fWeight(1) {
621 }
622
Hal Canaryfcf63592019-07-12 11:32:43 -0400623 MyClick(ClickType type, int index)
624 : fType(type)
caryclark64022c12016-05-27 05:13:26 -0700625 , fControl((ControlType) index)
626 , fVerb((SkPath::Verb) -1)
627 , fWeight(1) {
628 }
629
Hal Canaryfcf63592019-07-12 11:32:43 -0400630 MyClick(ClickType type, int index, SkPath::Verb verb, SkScalar weight)
631 : fType(type)
caryclark64022c12016-05-27 05:13:26 -0700632 , fControl((ControlType) index)
633 , fVerb(verb)
634 , fWeight(weight) {
635 }
636
637 bool isButton() {
638 return kFirstButton <= fControl && fControl <= kLastButton;
639 }
640
641 int ptHit() const {
642 SkASSERT(fType == kPtType);
643 return (int) fControl;
644 }
645
646 int verbHit() const {
647 SkASSERT(fType == kVerbType);
648 return (int) fControl;
649 }
650};
651
652enum {
653 kControlCount = MyClick::kLastControl - MyClick::kFirstControl + 1,
654};
655
656static struct ControlPair {
657 UniControl* fControl;
658 MyClick::ControlType fControlType;
659} kControlList[kControlCount];
660
661enum {
662 kButtonCount = MyClick::kLastButton - MyClick::kFirstButton + 1,
663 kVerbCount = MyClick::kLastVerbButton - MyClick::kFirstButton + 1,
664};
665
666static struct ButtonPair {
667 Button* fButton;
668 MyClick::ControlType fButtonType;
669} kButtonList[kButtonCount];
670
671static void enable_verb_button(MyClick::ControlType type) {
672 for (int index = 0; index < kButtonCount; ++index) {
673 MyClick::ControlType testType = kButtonList[index].fButtonType;
674 if (MyClick::kFirstButton <= testType && testType <= MyClick::kLastVerbButton) {
675 Button* button = kButtonList[index].fButton;
676 button->setEnabled(testType == type);
677 }
678 }
679}
680
681struct Stroke;
682
683struct Active {
684 Active* fNext;
685 Stroke* fParent;
686 SkScalar fStart;
687 SkScalar fEnd;
688
689 void reset() {
Ben Wagnera93a14a2017-08-28 10:34:05 -0400690 fNext = nullptr;
caryclark64022c12016-05-27 05:13:26 -0700691 fStart = 0;
692 fEnd = 1;
693 }
694};
695
696struct Stroke {
697 SkPath fPath;
698 Active fActive;
699 bool fInner;
700
701 void reset() {
702 fPath.reset();
703 fActive.reset();
704 }
705};
706
707struct PathUndo {
708 SkPath fPath;
Hal Canary8918d532019-07-12 10:04:14 -0400709 std::unique_ptr<PathUndo> fNext;
caryclark64022c12016-05-27 05:13:26 -0700710};
711
Ben Wagnerb2c4ea62018-08-08 11:36:17 -0400712class AAGeometryView : public Sample {
caryclark64022c12016-05-27 05:13:26 -0700713 SkPaint fActivePaint;
714 SkPaint fComplexPaint;
715 SkPaint fCoveragePaint;
Mike Reedb579f072019-01-03 15:45:53 -0500716 SkFont fLegendLeftFont;
717 SkFont fLegendRightFont;
caryclark64022c12016-05-27 05:13:26 -0700718 SkPaint fPointPaint;
719 SkPaint fSkeletonPaint;
720 SkPaint fLightSkeletonPaint;
721 SkPath fPath;
722 ControlPaints fControlPaints;
723 UniControl fResControl;
724 UniControl fWeightControl;
725 UniControl fWidthControl;
726 BiControl fFilterControl;
727 ButtonPaints fButtonPaints;
728 Button fCubicButton;
729 Button fConicButton;
730 Button fQuadButton;
731 Button fLineButton;
732 Button fAddButton;
733 Button fDeleteButton;
734 Button fFillButton;
735 Button fSkeletonButton;
736 Button fFilterButton;
737 Button fBisectButton;
738 Button fJoinButton;
739 Button fInOutButton;
740 SkTArray<Stroke> fStrokes;
Hal Canary8918d532019-07-12 10:04:14 -0400741 std::unique_ptr<PathUndo> fUndo;
caryclark64022c12016-05-27 05:13:26 -0700742 int fActivePt;
743 int fActiveVerb;
744 bool fHandlePathMove;
745 bool fShowLegend;
746 bool fHideAll;
Chris Dalton08d1a252017-10-20 11:46:47 -0600747 const int kHitToleranace = 25;
caryclark64022c12016-05-27 05:13:26 -0700748
749public:
750
Ben Wagner63fd7602017-10-09 15:45:33 -0400751 AAGeometryView()
caryclark64022c12016-05-27 05:13:26 -0700752 : fResControl("error", 0, 10)
753 , fWeightControl("weight", 0, 5)
754 , fWidthControl("width", FLT_EPSILON, 100)
755 , fFilterControl("filter", 0, 255)
756 , fCubicButton('C')
757 , fConicButton('K')
758 , fQuadButton('Q')
759 , fLineButton('L')
760 , fAddButton('+')
761 , fDeleteButton('x')
762 , fFillButton('p')
763 , fSkeletonButton('s')
764 , fFilterButton('f', 3)
765 , fBisectButton('b')
766 , fJoinButton('j')
767 , fInOutButton('|')
caryclark64022c12016-05-27 05:13:26 -0700768 , fActivePt(-1)
769 , fActiveVerb(-1)
770 , fHandlePathMove(true)
771 , fShowLegend(false)
772 , fHideAll(false)
773 {
774 fCoveragePaint.setAntiAlias(true);
775 fCoveragePaint.setColor(SK_ColorBLUE);
776 SkPaint strokePaint;
777 strokePaint.setAntiAlias(true);
778 strokePaint.setStyle(SkPaint::kStroke_Style);
779 fPointPaint = strokePaint;
780 fPointPaint.setColor(0x99ee3300);
781 fSkeletonPaint = strokePaint;
782 fSkeletonPaint.setColor(SK_ColorRED);
783 fLightSkeletonPaint = fSkeletonPaint;
784 fLightSkeletonPaint.setColor(0xFFFF7f7f);
785 fActivePaint = strokePaint;
786 fActivePaint.setColor(0x99ee3300);
787 fActivePaint.setStrokeWidth(5);
788 fComplexPaint = fActivePaint;
789 fComplexPaint.setColor(SK_ColorBLUE);
Mike Reedb579f072019-01-03 15:45:53 -0500790 fLegendLeftFont.setSize(13);
791 fLegendRightFont = fLegendLeftFont;
caryclark64022c12016-05-27 05:13:26 -0700792 construct_path(fPath);
793 fFillButton.fVisible = fSkeletonButton.fVisible = fFilterButton.fVisible
794 = fBisectButton.fVisible = fJoinButton.fVisible = fInOutButton.fVisible = true;
795 fSkeletonButton.setEnabled(true);
796 fInOutButton.setEnabled(true);
797 fJoinButton.setEnabled(true);
798 fFilterControl.fValLo = 120;
799 fFilterControl.fValHi = 141;
800 fFilterControl.fVisible = fFilterButton.fState == 2;
801 fResControl.fValLo = 5;
802 fResControl.fVisible = true;
803 fWidthControl.fValLo = 50;
804 fWidthControl.fVisible = true;
805 init_controlList();
806 init_buttonList();
807 }
808
Hal Canary8918d532019-07-12 10:04:14 -0400809 ~AAGeometryView() override {
810 // Free linked list without deep recursion.
811 std::unique_ptr<PathUndo> undo = std::move(fUndo);
812 while (undo) {
813 undo = std::move(undo->fNext);
814 }
815 }
816
caryclark64022c12016-05-27 05:13:26 -0700817 bool constructPath() {
818 construct_path(fPath);
caryclark64022c12016-05-27 05:13:26 -0700819 return true;
820 }
821
Hal Canaryb1f411a2019-08-29 10:39:22 -0400822 void savePath(skui::InputState state) {
823 if (state != skui::InputState::kDown) {
caryclark64022c12016-05-27 05:13:26 -0700824 return;
825 }
826 if (fUndo && fUndo->fPath == fPath) {
827 return;
828 }
Hal Canary8918d532019-07-12 10:04:14 -0400829 std::unique_ptr<PathUndo> undo(new PathUndo);
caryclark64022c12016-05-27 05:13:26 -0700830 undo->fPath = fPath;
Hal Canary8918d532019-07-12 10:04:14 -0400831 undo->fNext = std::move(fUndo);
832 fUndo = std::move(undo);
caryclark64022c12016-05-27 05:13:26 -0700833 }
834
835 bool undo() {
836 if (!fUndo) {
837 return false;
838 }
Hal Canary8918d532019-07-12 10:04:14 -0400839 fPath = std::move(fUndo->fPath);
840 fUndo = std::move(fUndo->fNext);
caryclark64022c12016-05-27 05:13:26 -0700841 validatePath();
caryclark64022c12016-05-27 05:13:26 -0700842 return true;
843 }
844
Hal Canary8918d532019-07-12 10:04:14 -0400845 void validatePath() {}
caryclark64022c12016-05-27 05:13:26 -0700846
847 void set_controlList(int index, UniControl* control, MyClick::ControlType type) {
848 kControlList[index].fControl = control;
849 kControlList[index].fControlType = type;
850 }
851
852 #define SET_CONTROL(Name) set_controlList(index++, &f##Name##Control, \
853 MyClick::k##Name##Control)
854
855 bool hideAll() {
856 fHideAll ^= true;
caryclark64022c12016-05-27 05:13:26 -0700857 return true;
858 }
859
860 void init_controlList() {
861 int index = 0;
862 SET_CONTROL(Width);
863 SET_CONTROL(Res);
864 SET_CONTROL(Filter);
865 SET_CONTROL(Weight);
Brian Osman16adfa32016-10-18 14:42:44 -0400866 }
Ben Wagner63fd7602017-10-09 15:45:33 -0400867
caryclark64022c12016-05-27 05:13:26 -0700868 #undef SET_CONTROL
869
870 void set_buttonList(int index, Button* button, MyClick::ControlType type) {
871 kButtonList[index].fButton = button;
872 kButtonList[index].fButtonType = type;
873 }
874
875 #define SET_BUTTON(Name) set_buttonList(index++, &f##Name##Button, \
876 MyClick::k##Name##Button)
877
878 void init_buttonList() {
879 int index = 0;
880 SET_BUTTON(Fill);
881 SET_BUTTON(Skeleton);
882 SET_BUTTON(Filter);
883 SET_BUTTON(Bisect);
884 SET_BUTTON(Join);
885 SET_BUTTON(InOut);
886 SET_BUTTON(Cubic);
887 SET_BUTTON(Conic);
888 SET_BUTTON(Quad);
889 SET_BUTTON(Line);
890 SET_BUTTON(Add);
891 SET_BUTTON(Delete);
892 }
893
894 #undef SET_BUTTON
895
Hal Canary8a027312019-07-03 10:55:44 -0400896 SkString name() override { return SkString("AAGeometry"); }
897
Hal Canary6cc65e12019-07-03 15:53:04 -0400898 bool onChar(SkUnichar) override;
Ben Wagner63fd7602017-10-09 15:45:33 -0400899
caryclark64022c12016-05-27 05:13:26 -0700900 void onSizeChange() override {
901 setControlButtonsPos();
902 this->INHERITED::onSizeChange();
903 }
904
905 bool pathDump() {
906 fPath.dump();
907 return true;
908 }
909
910 bool scaleDown() {
911 SkMatrix matrix;
912 SkRect bounds = fPath.getBounds();
913 matrix.setScale(1.f / 1.5f, 1.f / 1.5f, bounds.centerX(), bounds.centerY());
914 fPath.transform(matrix);
915 validatePath();
caryclark64022c12016-05-27 05:13:26 -0700916 return true;
917 }
918
919 bool scaleToFit() {
920 SkMatrix matrix;
921 SkRect bounds = fPath.getBounds();
Brian Osman788b9162020-02-07 10:36:46 -0500922 SkScalar scale = std::min(this->width() / bounds.width(), this->height() / bounds.height())
caryclark64022c12016-05-27 05:13:26 -0700923 * 0.8f;
924 matrix.setScale(scale, scale, bounds.centerX(), bounds.centerY());
925 fPath.transform(matrix);
926 bounds = fPath.getBounds();
927 SkScalar offsetX = (this->width() - bounds.width()) / 2 - bounds.fLeft;
928 SkScalar offsetY = (this->height() - bounds.height()) / 2 - bounds.fTop;
929 fPath.offset(offsetX, offsetY);
930 validatePath();
caryclark64022c12016-05-27 05:13:26 -0700931 return true;
932 }
933
934 bool scaleUp() {
935 SkMatrix matrix;
936 SkRect bounds = fPath.getBounds();
937 matrix.setScale(1.5f, 1.5f, bounds.centerX(), bounds.centerY());
938 fPath.transform(matrix);
939 validatePath();
caryclark64022c12016-05-27 05:13:26 -0700940 return true;
941 }
942
943 void setControlButtonsPos() {
944 SkScalar widthOffset = this->width() - 100;
945 for (int index = 0; index < kControlCount; ++index) {
946 if (kControlList[index].fControl->fVisible) {
947 kControlList[index].fControl->fBounds.setXYWH(widthOffset, 30, 30, 400);
948 widthOffset -= 50;
949 }
950 }
951 SkScalar buttonOffset = 0;
952 for (int index = 0; index < kButtonCount; ++index) {
953 kButtonList[index].fButton->fBounds.setXYWH(this->width() - 50,
954 buttonOffset += 50, 30, 30);
955 }
956 }
957
958 bool showLegend() {
959 fShowLegend ^= true;
caryclark64022c12016-05-27 05:13:26 -0700960 return true;
961 }
962
963 void draw_bisect(SkCanvas* canvas, const SkVector& lastVector, const SkVector& vector,
964 const SkPoint& pt) {
965 SkVector lastV = lastVector;
966 SkScalar lastLen = lastVector.length();
967 SkVector nextV = vector;
968 SkScalar nextLen = vector.length();
969 if (lastLen < nextLen) {
970 lastV.setLength(nextLen);
971 } else {
972 nextV.setLength(lastLen);
973 }
974
975 SkVector bisect = { (lastV.fX + nextV.fX) / 2, (lastV.fY + nextV.fY) / 2 };
976 bisect.setLength(fWidthControl.fValLo * 2);
977 if (fBisectButton.enabled()) {
Hal Canary23e474c2017-05-15 13:35:35 -0400978 canvas->drawLine(pt, pt + bisect, fSkeletonPaint);
caryclark64022c12016-05-27 05:13:26 -0700979 }
980 lastV.setLength(fWidthControl.fValLo);
981 if (fBisectButton.enabled()) {
Hal Canary23e474c2017-05-15 13:35:35 -0400982 canvas->drawLine(pt, {pt.fX - lastV.fY, pt.fY + lastV.fX}, fSkeletonPaint);
caryclark64022c12016-05-27 05:13:26 -0700983 }
984 nextV.setLength(fWidthControl.fValLo);
985 if (fBisectButton.enabled()) {
Hal Canary23e474c2017-05-15 13:35:35 -0400986 canvas->drawLine(pt, {pt.fX + nextV.fY, pt.fY - nextV.fX}, fSkeletonPaint);
caryclark64022c12016-05-27 05:13:26 -0700987 }
988 if (fJoinButton.enabled()) {
989 SkScalar r = fWidthControl.fValLo;
990 SkRect oval = { pt.fX - r, pt.fY - r, pt.fX + r, pt.fY + r};
991 SkScalar startAngle = SkScalarATan2(lastV.fX, -lastV.fY) * 180.f / SK_ScalarPI;
992 SkScalar endAngle = SkScalarATan2(-nextV.fX, nextV.fY) * 180.f / SK_ScalarPI;
993 if (endAngle > startAngle) {
994 canvas->drawArc(oval, startAngle, endAngle - startAngle, false, fSkeletonPaint);
995 } else {
996 canvas->drawArc(oval, startAngle, 360 - (startAngle - endAngle), false,
997 fSkeletonPaint);
998 }
999 }
1000 }
1001
1002 void draw_bisects(SkCanvas* canvas, bool activeOnly) {
1003 SkVector firstVector, lastVector, nextLast, vector;
1004 SkPoint pts[4];
1005 SkPoint firstPt = { 0, 0 }; // init to avoid warning;
1006 SkPath::Verb verb;
1007 SkPath::Iter iter(fPath, true);
1008 bool foundFirst = false;
1009 int counter = -1;
1010 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1011 ++counter;
1012 if (activeOnly && counter != fActiveVerb && counter - 1 != fActiveVerb
1013 && counter + 1 != fActiveVerb
1014 && (fActiveVerb != 1 || counter != fPath.countVerbs())) {
1015 continue;
1016 }
1017 switch (verb) {
1018 case SkPath::kLine_Verb:
1019 nextLast = pts[0] - pts[1];
1020 vector = pts[1] - pts[0];
1021 break;
1022 case SkPath::kQuad_Verb: {
1023 nextLast = pts[1] - pts[2];
1024 if (SkScalarNearlyZero(nextLast.length())) {
1025 nextLast = pts[0] - pts[2];
1026 }
1027 vector = pts[1] - pts[0];
1028 if (SkScalarNearlyZero(vector.length())) {
1029 vector = pts[2] - pts[0];
1030 }
1031 if (!fBisectButton.enabled()) {
1032 break;
1033 }
1034 SkScalar t = SkFindQuadMaxCurvature(pts);
1035 if (0 < t && t < 1) {
1036 SkPoint maxPt = SkEvalQuadAt(pts, t);
1037 SkVector tangent = SkEvalQuadTangentAt(pts, t);
1038 tangent.setLength(fWidthControl.fValLo * 2);
Hal Canary23e474c2017-05-15 13:35:35 -04001039 canvas->drawLine(maxPt, {maxPt.fX + tangent.fY, maxPt.fY - tangent.fX},
1040 fSkeletonPaint);
caryclark64022c12016-05-27 05:13:26 -07001041 }
1042 } break;
1043 case SkPath::kConic_Verb:
1044 nextLast = pts[1] - pts[2];
1045 if (SkScalarNearlyZero(nextLast.length())) {
1046 nextLast = pts[0] - pts[2];
1047 }
1048 vector = pts[1] - pts[0];
1049 if (SkScalarNearlyZero(vector.length())) {
1050 vector = pts[2] - pts[0];
1051 }
1052 if (!fBisectButton.enabled()) {
1053 break;
1054 }
1055 // FIXME : need max curvature or equivalent here
1056 break;
1057 case SkPath::kCubic_Verb: {
1058 nextLast = pts[2] - pts[3];
1059 if (SkScalarNearlyZero(nextLast.length())) {
1060 nextLast = pts[1] - pts[3];
1061 if (SkScalarNearlyZero(nextLast.length())) {
1062 nextLast = pts[0] - pts[3];
1063 }
1064 }
1065 vector = pts[0] - pts[1];
1066 if (SkScalarNearlyZero(vector.length())) {
1067 vector = pts[0] - pts[2];
1068 if (SkScalarNearlyZero(vector.length())) {
1069 vector = pts[0] - pts[3];
1070 }
1071 }
1072 if (!fBisectButton.enabled()) {
1073 break;
1074 }
1075 SkScalar tMax[2];
1076 int tMaxCount = SkFindCubicMaxCurvature(pts, tMax);
1077 for (int tIndex = 0; tIndex < tMaxCount; ++tIndex) {
1078 if (0 >= tMax[tIndex] || tMax[tIndex] >= 1) {
1079 continue;
1080 }
1081 SkPoint maxPt;
1082 SkVector tangent;
Ben Wagnera93a14a2017-08-28 10:34:05 -04001083 SkEvalCubicAt(pts, tMax[tIndex], &maxPt, &tangent, nullptr);
caryclark64022c12016-05-27 05:13:26 -07001084 tangent.setLength(fWidthControl.fValLo * 2);
Hal Canary23e474c2017-05-15 13:35:35 -04001085 canvas->drawLine(maxPt, {maxPt.fX + tangent.fY, maxPt.fY - tangent.fX},
1086 fSkeletonPaint);
caryclark64022c12016-05-27 05:13:26 -07001087 }
1088 } break;
1089 case SkPath::kClose_Verb:
1090 if (foundFirst) {
1091 draw_bisect(canvas, lastVector, firstVector, firstPt);
1092 foundFirst = false;
1093 }
1094 break;
Ben Wagner63fd7602017-10-09 15:45:33 -04001095 default:
caryclark64022c12016-05-27 05:13:26 -07001096 break;
1097 }
1098 if (SkPath::kLine_Verb <= verb && verb <= SkPath::kCubic_Verb) {
1099 if (!foundFirst) {
1100 firstPt = pts[0];
1101 firstVector = vector;
1102 foundFirst = true;
1103 } else {
1104 draw_bisect(canvas, lastVector, vector, pts[0]);
1105 }
1106 lastVector = nextLast;
1107 }
1108 }
1109 }
1110
1111 void draw_legend(SkCanvas* canvas);
1112
1113 void draw_segment(SkCanvas* canvas) {
1114 SkPoint pts[4];
1115 SkPath::Verb verb;
1116 SkPath::Iter iter(fPath, true);
1117 int counter = -1;
1118 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1119 if (++counter < fActiveVerb) {
1120 continue;
1121 }
1122 switch (verb) {
1123 case SkPath::kLine_Verb:
1124 canvas->drawPoints(SkCanvas::kLines_PointMode, 2, pts, fActivePaint);
1125 draw_points(canvas, pts, 2);
1126 break;
1127 case SkPath::kQuad_Verb: {
1128 SkPath qPath;
1129 qPath.moveTo(pts[0]);
1130 qPath.quadTo(pts[1], pts[2]);
1131 canvas->drawPath(qPath, fActivePaint);
1132 draw_points(canvas, pts, 3);
1133 } break;
1134 case SkPath::kConic_Verb: {
1135 SkPath conicPath;
1136 conicPath.moveTo(pts[0]);
1137 conicPath.conicTo(pts[1], pts[2], iter.conicWeight());
1138 canvas->drawPath(conicPath, fActivePaint);
1139 draw_points(canvas, pts, 3);
1140 } break;
1141 case SkPath::kCubic_Verb: {
Cary Clark7eb01e02016-12-08 14:36:32 -05001142 SkScalar loopT[3];
1143 int complex = SkDCubic::ComplexBreak(pts, loopT);
caryclark64022c12016-05-27 05:13:26 -07001144 SkPath cPath;
1145 cPath.moveTo(pts[0]);
1146 cPath.cubicTo(pts[1], pts[2], pts[3]);
1147 canvas->drawPath(cPath, complex ? fComplexPaint : fActivePaint);
1148 draw_points(canvas, pts, 4);
1149 } break;
Ben Wagner63fd7602017-10-09 15:45:33 -04001150 default:
caryclark64022c12016-05-27 05:13:26 -07001151 break;
1152 }
1153 return;
1154 }
1155 }
1156
1157 void draw_points(SkCanvas* canvas, SkPoint* points, int count) {
1158 for (int index = 0; index < count; ++index) {
1159 canvas->drawCircle(points[index].fX, points[index].fY, 10, fPointPaint);
1160 }
1161 }
1162
1163 int hittest_verb(SkPoint pt, SkPath::Verb* verbPtr, SkScalar* weight) {
1164 SkIntersections i;
1165 SkDLine hHit = {{{pt.fX - kHitToleranace, pt.fY }, {pt.fX + kHitToleranace, pt.fY}}};
1166 SkDLine vHit = {{{pt.fX, pt.fY - kHitToleranace }, {pt.fX, pt.fY + kHitToleranace}}};
1167 SkPoint pts[4];
1168 SkPath::Verb verb;
1169 SkPath::Iter iter(fPath, true);
1170 int counter = -1;
1171 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1172 ++counter;
1173 switch (verb) {
1174 case SkPath::kLine_Verb: {
1175 SkDLine line;
1176 line.set(pts);
1177 if (i.intersect(line, hHit) || i.intersect(line, vHit)) {
1178 *verbPtr = verb;
1179 *weight = 1;
1180 return counter;
1181 }
1182 } break;
1183 case SkPath::kQuad_Verb: {
1184 SkDQuad quad;
1185 quad.set(pts);
1186 if (i.intersect(quad, hHit) || i.intersect(quad, vHit)) {
1187 *verbPtr = verb;
1188 *weight = 1;
1189 return counter;
1190 }
1191 } break;
1192 case SkPath::kConic_Verb: {
1193 SkDConic conic;
1194 SkScalar w = iter.conicWeight();
1195 conic.set(pts, w);
1196 if (i.intersect(conic, hHit) || i.intersect(conic, vHit)) {
1197 *verbPtr = verb;
1198 *weight = w;
1199 return counter;
1200 }
1201 } break;
1202 case SkPath::kCubic_Verb: {
1203 SkDCubic cubic;
1204 cubic.set(pts);
1205 if (i.intersect(cubic, hHit) || i.intersect(cubic, vHit)) {
1206 *verbPtr = verb;
1207 *weight = 1;
1208 return counter;
1209 }
1210 } break;
Ben Wagner63fd7602017-10-09 15:45:33 -04001211 default:
caryclark64022c12016-05-27 05:13:26 -07001212 break;
1213 }
1214 }
1215 return -1;
1216 }
1217
1218 SkScalar pt_to_line(SkPoint s, SkPoint e, int x, int y) {
1219 SkScalar radius = fWidthControl.fValLo;
1220 SkVector adjOpp = e - s;
Cary Clarkdf429f32017-11-08 11:44:31 -05001221 SkScalar lenSq = SkPointPriv::LengthSqd(adjOpp);
caryclark64022c12016-05-27 05:13:26 -07001222 SkPoint rotated = {
1223 (y - s.fY) * adjOpp.fY + (x - s.fX) * adjOpp.fX,
1224 (y - s.fY) * adjOpp.fX - (x - s.fX) * adjOpp.fY,
1225 };
1226 if (rotated.fX < 0 || rotated.fX > lenSq) {
1227 return -radius;
1228 }
1229 rotated.fY /= SkScalarSqrt(lenSq);
Brian Osman788b9162020-02-07 10:36:46 -05001230 return std::max(-radius, std::min(radius, rotated.fY));
caryclark64022c12016-05-27 05:13:26 -07001231 }
1232
1233 // given a line, compute the interior and exterior gradient coverage
1234 bool coverage(SkPoint s, SkPoint e, uint8_t* distanceMap, int w, int h) {
1235 SkScalar radius = fWidthControl.fValLo;
Brian Osman788b9162020-02-07 10:36:46 -05001236 int minX = std::max(0, (int) (std::min(s.fX, e.fX) - radius));
1237 int minY = std::max(0, (int) (std::min(s.fY, e.fY) - radius));
1238 int maxX = std::min(w, (int) (std::max(s.fX, e.fX) + radius) + 1);
1239 int maxY = std::min(h, (int) (std::max(s.fY, e.fY) + radius) + 1);
caryclark64022c12016-05-27 05:13:26 -07001240 for (int y = minY; y < maxY; ++y) {
1241 for (int x = minX; x < maxX; ++x) {
1242 SkScalar ptToLineDist = pt_to_line(s, e, x, y);
1243 if (ptToLineDist > -radius && ptToLineDist < radius) {
1244 SkScalar coverage = ptToLineDist / radius;
1245 add_to_map(1 - SkScalarAbs(coverage), x, y, distanceMap, w, h);
1246 }
1247 SkVector ptToS = { x - s.fX, y - s.fY };
1248 SkScalar dist = ptToS.length();
1249 if (dist < radius) {
1250 SkScalar coverage = dist / radius;
1251 add_to_map(1 - SkScalarAbs(coverage), x, y, distanceMap, w, h);
1252 }
1253 SkVector ptToE = { x - e.fX, y - e.fY };
1254 dist = ptToE.length();
1255 if (dist < radius) {
1256 SkScalar coverage = dist / radius;
1257 add_to_map(1 - SkScalarAbs(coverage), x, y, distanceMap, w, h);
1258 }
1259 }
1260 }
1261 return true;
1262 }
1263
1264 void quad_coverage(SkPoint pts[3], uint8_t* distanceMap, int w, int h) {
1265 SkScalar dist = pts[0].Distance(pts[0], pts[2]);
1266 if (dist < gCurveDistance) {
1267 (void) coverage(pts[0], pts[2], distanceMap, w, h);
1268 return;
1269 }
1270 SkPoint split[5];
1271 SkChopQuadAt(pts, split, 0.5f);
1272 quad_coverage(&split[0], distanceMap, w, h);
1273 quad_coverage(&split[2], distanceMap, w, h);
1274 }
1275
1276 void conic_coverage(SkPoint pts[3], SkScalar weight, uint8_t* distanceMap, int w, int h) {
1277 SkScalar dist = pts[0].Distance(pts[0], pts[2]);
1278 if (dist < gCurveDistance) {
1279 (void) coverage(pts[0], pts[2], distanceMap, w, h);
1280 return;
1281 }
1282 SkConic split[2];
1283 SkConic conic;
1284 conic.set(pts, weight);
caryclark414c4292016-09-26 11:03:54 -07001285 if (conic.chopAt(0.5f, split)) {
1286 conic_coverage(split[0].fPts, split[0].fW, distanceMap, w, h);
1287 conic_coverage(split[1].fPts, split[1].fW, distanceMap, w, h);
1288 }
caryclark64022c12016-05-27 05:13:26 -07001289 }
1290
1291 void cubic_coverage(SkPoint pts[4], uint8_t* distanceMap, int w, int h) {
1292 SkScalar dist = pts[0].Distance(pts[0], pts[3]);
1293 if (dist < gCurveDistance) {
1294 (void) coverage(pts[0], pts[3], distanceMap, w, h);
1295 return;
1296 }
1297 SkPoint split[7];
1298 SkChopCubicAt(pts, split, 0.5f);
1299 cubic_coverage(&split[0], distanceMap, w, h);
1300 cubic_coverage(&split[3], distanceMap, w, h);
1301 }
1302
1303 void path_coverage(const SkPath& path, uint8_t* distanceMap, int w, int h) {
1304 memset(distanceMap, 0, sizeof(distanceMap[0]) * w * h);
1305 SkPoint pts[4];
1306 SkPath::Verb verb;
1307 SkPath::Iter iter(path, true);
1308 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1309 switch (verb) {
1310 case SkPath::kLine_Verb:
1311 (void) coverage(pts[0], pts[1], distanceMap, w, h);
1312 break;
1313 case SkPath::kQuad_Verb:
1314 quad_coverage(pts, distanceMap, w, h);
1315 break;
1316 case SkPath::kConic_Verb:
1317 conic_coverage(pts, iter.conicWeight(), distanceMap, w, h);
1318 break;
1319 case SkPath::kCubic_Verb:
1320 cubic_coverage(pts, distanceMap, w, h);
1321 break;
Ben Wagner63fd7602017-10-09 15:45:33 -04001322 default:
caryclark64022c12016-05-27 05:13:26 -07001323 break;
1324 }
1325 }
1326 }
1327
1328 static uint8_t* set_up_dist_map(const SkImageInfo& imageInfo, SkBitmap* distMap) {
1329 distMap->setInfo(imageInfo);
1330 distMap->setIsVolatile(true);
1331 SkAssertResult(distMap->tryAllocPixels());
1332 SkASSERT((int) distMap->rowBytes() == imageInfo.width());
1333 return distMap->getAddr8(0, 0);
1334 }
1335
1336 void path_stroke(int index, SkPath* inner, SkPath* outer) {
1337 #if 0
1338 SkPathStroker stroker(fPath, fWidthControl.fValLo, 0,
1339 SkPaint::kRound_Cap, SkPaint::kRound_Join, fResControl.fValLo);
1340 SkPoint pts[4], firstPt, lastPt;
1341 SkPath::Verb verb;
1342 SkPath::Iter iter(fPath, true);
1343 int counter = -1;
1344 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1345 ++counter;
1346 switch (verb) {
1347 case SkPath::kMove_Verb:
1348 firstPt = pts[0];
1349 break;
1350 case SkPath::kLine_Verb:
1351 if (counter == index) {
1352 stroker.moveTo(pts[0]);
1353 stroker.lineTo(pts[1]);
1354 goto done;
1355 }
1356 lastPt = pts[1];
1357 break;
1358 case SkPath::kQuad_Verb:
1359 if (counter == index) {
1360 stroker.moveTo(pts[0]);
1361 stroker.quadTo(pts[1], pts[2]);
1362 goto done;
1363 }
1364 lastPt = pts[2];
1365 break;
1366 case SkPath::kConic_Verb:
1367 if (counter == index) {
1368 stroker.moveTo(pts[0]);
1369 stroker.conicTo(pts[1], pts[2], iter.conicWeight());
1370 goto done;
1371 }
1372 lastPt = pts[2];
1373 break;
1374 case SkPath::kCubic_Verb:
1375 if (counter == index) {
1376 stroker.moveTo(pts[0]);
1377 stroker.cubicTo(pts[1], pts[2], pts[3]);
1378 goto done;
1379 }
1380 lastPt = pts[3];
1381 break;
1382 case SkPath::kClose_Verb:
1383 if (counter == index) {
1384 stroker.moveTo(lastPt);
1385 stroker.lineTo(firstPt);
1386 goto done;
1387 }
1388 break;
1389 case SkPath::kDone_Verb:
1390 break;
1391 default:
1392 SkASSERT(0);
1393 }
1394 }
1395 done:
1396 *inner = stroker.fInner;
1397 *outer = stroker.fOuter;
1398#endif
1399 }
1400
1401 void draw_stroke(SkCanvas* canvas, int active) {
1402 SkPath inner, outer;
1403 path_stroke(active, &inner, &outer);
1404 canvas->drawPath(inner, fSkeletonPaint);
1405 canvas->drawPath(outer, fSkeletonPaint);
1406 }
1407
1408 void gather_strokes() {
1409 fStrokes.reset();
1410 for (int index = 0; index < fPath.countVerbs(); ++index) {
1411 Stroke& inner = fStrokes.push_back();
1412 inner.reset();
1413 inner.fInner = true;
1414 Stroke& outer = fStrokes.push_back();
1415 outer.reset();
1416 outer.fInner = false;
1417 path_stroke(index, &inner.fPath, &outer.fPath);
1418 }
1419 }
1420
1421 void trim_strokes() {
1422 // eliminate self-itersecting loops
1423 // trim outside edges
1424 gather_strokes();
1425 for (int index = 0; index < fStrokes.count(); ++index) {
1426 SkPath& outPath = fStrokes[index].fPath;
1427 for (int inner = 0; inner < fStrokes.count(); ++inner) {
1428 if (index == inner) {
1429 continue;
1430 }
1431 SkPath& inPath = fStrokes[inner].fPath;
1432 if (!outPath.getBounds().intersects(inPath.getBounds())) {
1433 continue;
1434 }
Ben Wagner63fd7602017-10-09 15:45:33 -04001435
caryclark64022c12016-05-27 05:13:26 -07001436 }
1437 }
1438 }
1439
1440 void onDrawContent(SkCanvas* canvas) override {
1441#if 0
1442 SkDEBUGCODE(SkDebugStrokeGlobals debugGlobals);
1443 SkOpAA aaResult(fPath, fWidthControl.fValLo, fResControl.fValLo
1444 SkDEBUGPARAMS(&debugGlobals));
1445#endif
1446 SkPath strokePath;
1447// aaResult.simplify(&strokePath);
1448 canvas->drawPath(strokePath, fSkeletonPaint);
1449 SkRect bounds = fPath.getBounds();
1450 SkScalar radius = fWidthControl.fValLo;
1451 int w = (int) (bounds.fRight + radius + 1);
1452 int h = (int) (bounds.fBottom + radius + 1);
1453 SkImageInfo imageInfo = SkImageInfo::MakeA8(w, h);
1454 SkBitmap distMap;
1455 uint8_t* distanceMap = set_up_dist_map(imageInfo, &distMap);
1456 path_coverage(fPath, distanceMap, w, h);
1457 if (fFillButton.enabled()) {
1458 canvas->drawPath(fPath, fCoveragePaint);
1459 }
1460 if (fFilterButton.fState == 2
1461 && (0 < fFilterControl.fValLo || fFilterControl.fValHi < 255)) {
1462 SkBitmap filteredMap;
1463 uint8_t* filtered = set_up_dist_map(imageInfo, &filteredMap);
1464 filter_coverage(distanceMap, sizeof(uint8_t) * w * h, (uint8_t) fFilterControl.fValLo,
1465 (uint8_t) fFilterControl.fValHi, filtered);
1466 canvas->drawBitmap(filteredMap, 0, 0, &fCoveragePaint);
1467 } else if (fFilterButton.enabled()) {
1468 canvas->drawBitmap(distMap, 0, 0, &fCoveragePaint);
1469 }
1470 if (fSkeletonButton.enabled()) {
1471 canvas->drawPath(fPath, fActiveVerb >= 0 ? fLightSkeletonPaint : fSkeletonPaint);
1472 }
1473 if (fActiveVerb >= 0) {
1474 draw_segment(canvas);
1475 }
1476 if (fBisectButton.enabled() || fJoinButton.enabled()) {
1477 draw_bisects(canvas, fActiveVerb >= 0);
1478 }
1479 if (fInOutButton.enabled()) {
1480 if (fActiveVerb >= 0) {
1481 draw_stroke(canvas, fActiveVerb);
1482 } else {
1483 for (int index = 0; index < fPath.countVerbs(); ++index) {
1484 draw_stroke(canvas, index);
1485 }
1486 }
1487 }
1488 if (fHideAll) {
1489 return;
1490 }
1491 for (int index = 0; index < kControlCount; ++index) {
1492 kControlList[index].fControl->draw(canvas, fControlPaints);
1493 }
1494 for (int index = 0; index < kButtonCount; ++index) {
1495 kButtonList[index].fButton->draw(canvas, fButtonPaints);
1496 }
1497 if (fShowLegend) {
1498 draw_legend(canvas);
1499 }
1500
1501#if 0
1502 SkPaint paint;
1503 paint.setARGB(255, 34, 31, 31);
1504 paint.setAntiAlias(true);
1505
1506 SkPath path;
1507 path.moveTo(18,439);
1508 path.lineTo(414,439);
1509 path.lineTo(414,702);
1510 path.lineTo(18,702);
1511 path.lineTo(18,439);
1512
1513 path.moveTo(19,701);
1514 path.lineTo(413,701);
1515 path.lineTo(413,440);
1516 path.lineTo(19,440);
1517 path.lineTo(19,701);
1518 path.close();
1519 canvas->drawPath(path, paint);
1520
1521 canvas->scale(1.0f, -1.0f);
1522 canvas->translate(0.0f, -800.0f);
1523 canvas->drawPath(path, paint);
1524#endif
1525
1526 }
1527
1528 int hittest_pt(SkPoint pt) {
1529 for (int index = 0; index < fPath.countPoints(); ++index) {
1530 if (SkPoint::Distance(fPath.getPoint(index), pt) <= kHitToleranace * 2) {
1531 return index;
1532 }
1533 }
1534 return -1;
1535 }
1536
Hal Canaryb1f411a2019-08-29 10:39:22 -04001537 virtual Sample::Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey modi) override {
caryclark64022c12016-05-27 05:13:26 -07001538 SkPoint pt = {x, y};
1539 int ptHit = hittest_pt(pt);
1540 if (ptHit >= 0) {
Hal Canaryfcf63592019-07-12 11:32:43 -04001541 return new MyClick(MyClick::kPtType, ptHit);
caryclark64022c12016-05-27 05:13:26 -07001542 }
1543 SkPath::Verb verb;
1544 SkScalar weight;
1545 int verbHit = hittest_verb(pt, &verb, &weight);
1546 if (verbHit >= 0) {
Hal Canaryfcf63592019-07-12 11:32:43 -04001547 return new MyClick(MyClick::kVerbType, verbHit, verb, weight);
caryclark64022c12016-05-27 05:13:26 -07001548 }
1549 if (!fHideAll) {
1550 const SkRect& rectPt = SkRect::MakeXYWH(x, y, 1, 1);
1551 for (int index = 0; index < kControlCount; ++index) {
1552 if (kControlList[index].fControl->contains(rectPt)) {
Hal Canaryfcf63592019-07-12 11:32:43 -04001553 return new MyClick(MyClick::kControlType,
caryclark64022c12016-05-27 05:13:26 -07001554 kControlList[index].fControlType);
1555 }
1556 }
1557 for (int index = 0; index < kButtonCount; ++index) {
1558 if (kButtonList[index].fButton->contains(rectPt)) {
Hal Canaryfcf63592019-07-12 11:32:43 -04001559 return new MyClick(MyClick::kControlType, kButtonList[index].fButtonType);
caryclark64022c12016-05-27 05:13:26 -07001560 }
1561 }
1562 }
1563 fLineButton.fVisible = fQuadButton.fVisible = fConicButton.fVisible
1564 = fCubicButton.fVisible = fWeightControl.fVisible = fAddButton.fVisible
1565 = fDeleteButton.fVisible = false;
1566 fActiveVerb = -1;
1567 fActivePt = -1;
1568 if (fHandlePathMove) {
Hal Canaryfcf63592019-07-12 11:32:43 -04001569 return new MyClick(MyClick::kPathType, MyClick::kPathMove);
caryclark64022c12016-05-27 05:13:26 -07001570 }
Hal Canaryfcf63592019-07-12 11:32:43 -04001571 return nullptr;
caryclark64022c12016-05-27 05:13:26 -07001572 }
1573
1574 static SkScalar MapScreenYtoValue(int y, const UniControl& control) {
Brian Osman788b9162020-02-07 10:36:46 -05001575 return std::min(1.f, std::max(0.f,
caryclark64022c12016-05-27 05:13:26 -07001576 SkIntToScalar(y) - control.fBounds.fTop) / control.fBounds.height())
1577 * (control.fMax - control.fMin) + control.fMin;
1578 }
1579
1580 bool onClick(Click* click) override {
1581 MyClick* myClick = (MyClick*) click;
1582 switch (myClick->fType) {
1583 case MyClick::kPtType: {
1584 savePath(click->fState);
1585 fActivePt = myClick->ptHit();
1586 SkPoint pt = fPath.getPoint((int) myClick->fControl);
Hal Canaryfcf63592019-07-12 11:32:43 -04001587 pt.offset(SkIntToScalar(click->fCurr.fX - click->fPrev.fX),
1588 SkIntToScalar(click->fCurr.fY - click->fPrev.fY));
Chris Dalton8d3eb242020-05-04 10:43:33 -06001589 SkPathPriv::UpdatePathPoint(&fPath, fActivePt, pt);
caryclark64022c12016-05-27 05:13:26 -07001590 validatePath();
caryclark64022c12016-05-27 05:13:26 -07001591 return true;
1592 }
1593 case MyClick::kPathType:
1594 savePath(click->fState);
Hal Canaryfcf63592019-07-12 11:32:43 -04001595 fPath.offset(SkIntToScalar(click->fCurr.fX - click->fPrev.fX),
1596 SkIntToScalar(click->fCurr.fY - click->fPrev.fY));
caryclark64022c12016-05-27 05:13:26 -07001597 validatePath();
caryclark64022c12016-05-27 05:13:26 -07001598 return true;
1599 case MyClick::kVerbType: {
1600 fActiveVerb = myClick->verbHit();
1601 fLineButton.fVisible = fQuadButton.fVisible = fConicButton.fVisible
1602 = fCubicButton.fVisible = fAddButton.fVisible = fDeleteButton.fVisible
1603 = true;
1604 fLineButton.setEnabled(myClick->fVerb == SkPath::kLine_Verb);
1605 fQuadButton.setEnabled(myClick->fVerb == SkPath::kQuad_Verb);
1606 fConicButton.setEnabled(myClick->fVerb == SkPath::kConic_Verb);
1607 fCubicButton.setEnabled(myClick->fVerb == SkPath::kCubic_Verb);
1608 fWeightControl.fValLo = myClick->fWeight;
1609 fWeightControl.fVisible = myClick->fVerb == SkPath::kConic_Verb;
1610 } break;
1611 case MyClick::kControlType: {
Hal Canaryb1f411a2019-08-29 10:39:22 -04001612 if (click->fState != skui::InputState::kDown && myClick->isButton()) {
caryclark64022c12016-05-27 05:13:26 -07001613 return true;
1614 }
1615 switch (myClick->fControl) {
1616 case MyClick::kFilterControl: {
Hal Canaryfcf63592019-07-12 11:32:43 -04001617 SkScalar val = MapScreenYtoValue(click->fCurr.fY, fFilterControl);
caryclark64022c12016-05-27 05:13:26 -07001618 if (val - fFilterControl.fValLo < fFilterControl.fValHi - val) {
Brian Osman788b9162020-02-07 10:36:46 -05001619 fFilterControl.fValLo = std::max(0.f, val);
caryclark64022c12016-05-27 05:13:26 -07001620 } else {
Brian Osman788b9162020-02-07 10:36:46 -05001621 fFilterControl.fValHi = std::min(255.f, val);
caryclark64022c12016-05-27 05:13:26 -07001622 }
1623 } break;
1624 case MyClick::kResControl:
Hal Canaryfcf63592019-07-12 11:32:43 -04001625 fResControl.fValLo = MapScreenYtoValue(click->fCurr.fY, fResControl);
caryclark64022c12016-05-27 05:13:26 -07001626 break;
1627 case MyClick::kWeightControl: {
1628 savePath(click->fState);
Hal Canaryfcf63592019-07-12 11:32:43 -04001629 SkScalar w = MapScreenYtoValue(click->fCurr.fY, fWeightControl);
caryclark64022c12016-05-27 05:13:26 -07001630 set_path_weight(fActiveVerb, w, &fPath);
1631 validatePath();
1632 fWeightControl.fValLo = w;
1633 } break;
1634 case MyClick::kWidthControl:
Hal Canaryfcf63592019-07-12 11:32:43 -04001635 fWidthControl.fValLo = MapScreenYtoValue(click->fCurr.fY, fWidthControl);
caryclark64022c12016-05-27 05:13:26 -07001636 break;
1637 case MyClick::kLineButton:
1638 savePath(click->fState);
1639 enable_verb_button(myClick->fControl);
1640 fWeightControl.fVisible = false;
1641 set_path_verb(fActiveVerb, SkPath::kLine_Verb, &fPath, 1);
1642 validatePath();
1643 break;
1644 case MyClick::kQuadButton:
1645 savePath(click->fState);
1646 enable_verb_button(myClick->fControl);
1647 fWeightControl.fVisible = false;
1648 set_path_verb(fActiveVerb, SkPath::kQuad_Verb, &fPath, 1);
1649 validatePath();
1650 break;
1651 case MyClick::kConicButton: {
1652 savePath(click->fState);
1653 enable_verb_button(myClick->fControl);
1654 fWeightControl.fVisible = true;
1655 const SkScalar defaultConicWeight = 1.f / SkScalarSqrt(2);
1656 set_path_verb(fActiveVerb, SkPath::kConic_Verb, &fPath, defaultConicWeight);
1657 validatePath();
1658 fWeightControl.fValLo = get_path_weight(fActiveVerb, fPath);
1659 } break;
1660 case MyClick::kCubicButton:
1661 savePath(click->fState);
1662 enable_verb_button(myClick->fControl);
1663 fWeightControl.fVisible = false;
1664 set_path_verb(fActiveVerb, SkPath::kCubic_Verb, &fPath, 1);
1665 validatePath();
1666 break;
1667 case MyClick::kAddButton:
1668 savePath(click->fState);
1669 add_path_segment(fActiveVerb, &fPath);
1670 validatePath();
1671 if (fWeightControl.fVisible) {
1672 fWeightControl.fValLo = get_path_weight(fActiveVerb, fPath);
1673 }
1674 break;
1675 case MyClick::kDeleteButton:
1676 savePath(click->fState);
1677 delete_path_segment(fActiveVerb, &fPath);
1678 validatePath();
1679 break;
1680 case MyClick::kFillButton:
1681 fFillButton.toggle();
1682 break;
1683 case MyClick::kSkeletonButton:
1684 fSkeletonButton.toggle();
1685 break;
1686 case MyClick::kFilterButton:
1687 fFilterButton.toggle();
1688 fFilterControl.fVisible = fFilterButton.fState == 2;
1689 break;
1690 case MyClick::kBisectButton:
1691 fBisectButton.toggle();
1692 break;
1693 case MyClick::kJoinButton:
1694 fJoinButton.toggle();
1695 break;
1696 case MyClick::kInOutButton:
1697 fInOutButton.toggle();
1698 break;
1699 default:
1700 SkASSERT(0);
1701 break;
1702 }
1703 } break;
1704 default:
1705 SkASSERT(0);
1706 break;
1707 }
1708 setControlButtonsPos();
caryclark64022c12016-05-27 05:13:26 -07001709 return true;
1710 }
1711
1712private:
Ben Wagnerb2c4ea62018-08-08 11:36:17 -04001713 typedef Sample INHERITED;
caryclark64022c12016-05-27 05:13:26 -07001714};
1715
1716static struct KeyCommand {
1717 char fKey;
1718 char fAlternate;
1719 const char* fDescriptionL;
1720 const char* fDescriptionR;
1721 bool (AAGeometryView::*fFunction)();
1722} kKeyCommandList[] = {
Ben Wagner63fd7602017-10-09 15:45:33 -04001723 { ' ', 0, "space", "center path", &AAGeometryView::scaleToFit },
1724 { '-', 0, "-", "zoom out", &AAGeometryView::scaleDown },
1725 { '+', '=', "+/=", "zoom in", &AAGeometryView::scaleUp },
Chris Dalton08d1a252017-10-20 11:46:47 -06001726 { 'D', 0, "D", "dump to console", &AAGeometryView::pathDump },
1727 { 'H', 0, "H", "hide controls", &AAGeometryView::hideAll },
1728 { 'R', 0, "R", "reset path", &AAGeometryView::constructPath },
1729 { 'Z', 0, "Z", "undo", &AAGeometryView::undo },
caryclark64022c12016-05-27 05:13:26 -07001730 { '?', 0, "?", "show legend", &AAGeometryView::showLegend },
1731};
1732
1733const int kKeyCommandCount = (int) SK_ARRAY_COUNT(kKeyCommandList);
1734
1735void AAGeometryView::draw_legend(SkCanvas* canvas) {
1736 SkScalar bottomOffset = this->height() - 10;
1737 for (int index = kKeyCommandCount - 1; index >= 0; --index) {
1738 bottomOffset -= 15;
Mike Reedb579f072019-01-03 15:45:53 -05001739 SkTextUtils::DrawString(canvas, kKeyCommandList[index].fDescriptionL, this->width() - 160, bottomOffset,
1740 fLegendLeftFont, SkPaint());
Mike Reeda697df92018-10-26 07:28:30 -04001741 SkTextUtils::DrawString(canvas, kKeyCommandList[index].fDescriptionR,
Cary Clark2a475ea2017-04-28 15:35:12 -04001742 this->width() - 20, bottomOffset,
Mike Reedb579f072019-01-03 15:45:53 -05001743 fLegendRightFont, SkPaint(), SkTextUtils::kRight_Align);
caryclark64022c12016-05-27 05:13:26 -07001744 }
1745}
1746
Hal Canary6cc65e12019-07-03 15:53:04 -04001747bool AAGeometryView::onChar(SkUnichar uni) {
caryclark64022c12016-05-27 05:13:26 -07001748 for (int index = 0; index < kButtonCount; ++index) {
1749 Button* button = kButtonList[index].fButton;
1750 if (button->fVisible && uni == button->fLabel) {
Hal Canaryfcf63592019-07-12 11:32:43 -04001751 MyClick click(MyClick::kControlType, kButtonList[index].fButtonType);
Hal Canaryb1f411a2019-08-29 10:39:22 -04001752 click.fState = skui::InputState::kDown;
caryclark64022c12016-05-27 05:13:26 -07001753 (void) this->onClick(&click);
1754 return true;
1755 }
1756 }
1757 for (int index = 0; index < kKeyCommandCount; ++index) {
1758 KeyCommand& keyCommand = kKeyCommandList[index];
1759 if (uni == keyCommand.fKey || uni == keyCommand.fAlternate) {
1760 return (this->*keyCommand.fFunction)();
1761 }
1762 }
1763 if (('A' <= uni && uni <= 'Z') || ('a' <= uni && uni <= 'z')) {
1764 for (int index = 0; index < kButtonCount; ++index) {
1765 Button* button = kButtonList[index].fButton;
1766 if (button->fVisible && (uni & ~0x20) == (button->fLabel & ~0x20)) {
Hal Canaryfcf63592019-07-12 11:32:43 -04001767 MyClick click(MyClick::kControlType, kButtonList[index].fButtonType);
Hal Canaryb1f411a2019-08-29 10:39:22 -04001768 click.fState = skui::InputState::kDown;
caryclark64022c12016-05-27 05:13:26 -07001769 (void) this->onClick(&click);
1770 return true;
1771 }
1772 }
1773 }
Hal Canary6cc65e12019-07-03 15:53:04 -04001774 return false;
caryclark64022c12016-05-27 05:13:26 -07001775}
Ben Wagner63fd7602017-10-09 15:45:33 -04001776
caryclark64022c12016-05-27 05:13:26 -07001777DEF_SAMPLE( return new AAGeometryView; )