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