blob: bc708ea8be044007788051510c6075367387fb4d [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;
281 case SkPath::kQuad_Verb:
282 pts[2] = pts[1];
283 pts[1].fX = (pts[0].fX + pts[2].fX) / 2;
284 pts[1].fY = (pts[0].fY + pts[2].fY) / 2;
285 break;
286 case SkPath::kCubic_Verb:
287 pts[3] = pts[1];
288 pts[1].fX = (pts[0].fX * 2 + pts[3].fX) / 3;
289 pts[1].fY = (pts[0].fY * 2 + pts[3].fY) / 3;
290 pts[2].fX = (pts[0].fX + pts[3].fX * 2) / 3;
291 pts[2].fY = (pts[0].fY + pts[3].fY * 2) / 3;
292 break;
293 default:
294 SkASSERT(0);
295 break;
296 }
297 break;
298 case SkPath::kQuad_Verb:
299 case SkPath::kConic_Verb:
300 switch (v) {
301 case SkPath::kLine_Verb:
302 pts[1] = pts[2];
303 break;
304 case SkPath::kConic_Verb:
305 weight = w;
306 case SkPath::kQuad_Verb:
307 break;
308 case SkPath::kCubic_Verb: {
309 SkDQuad dQuad;
310 dQuad.set(pts);
311 SkDCubic dCubic = dQuad.debugToCubic();
312 pts[3] = pts[2];
313 pts[1] = dCubic[1].asSkPoint();
314 pts[2] = dCubic[2].asSkPoint();
315 } break;
316 default:
317 SkASSERT(0);
318 break;
319 }
320 break;
321 case SkPath::kCubic_Verb:
322 switch (v) {
323 case SkPath::kLine_Verb:
324 pts[1] = pts[3];
325 break;
326 case SkPath::kConic_Verb:
327 weight = w;
328 case SkPath::kQuad_Verb: {
329 SkDCubic dCubic;
330 dCubic.set(pts);
331 SkDQuad dQuad = dCubic.toQuad();
332 pts[1] = dQuad[1].asSkPoint();
333 pts[2] = pts[3];
334 } break;
335 default:
336 SkASSERT(0);
337 break;
338 }
339 break;
340 default:
341 SkASSERT(0);
342 break;
343 }
344 verb = v;
345 }
346 switch (verb) {
347 case SkPath::kMove_Verb:
348 result.moveTo(pts[0]);
349 break;
350 case SkPath::kLine_Verb:
351 result.lineTo(pts[1]);
352 break;
353 case SkPath::kQuad_Verb:
354 result.quadTo(pts[1], pts[2]);
355 break;
356 case SkPath::kConic_Verb:
357 result.conicTo(pts[1], pts[2], weight);
358 break;
359 case SkPath::kCubic_Verb:
360 result.cubicTo(pts[1], pts[2], pts[3]);
361 break;
362 case SkPath::kClose_Verb:
363 result.close();
364 break;
365 default:
366 SkASSERT(0);
367 break;
368 }
369 }
370 *path = result;
371}
372
373static void add_to_map(SkScalar coverage, int x, int y, uint8_t* distanceMap, int w, int h) {
374 int byteCoverage = (int) (coverage * 256);
375 if (byteCoverage < 0) {
376 byteCoverage = 0;
377 } else if (byteCoverage > 255) {
378 byteCoverage = 255;
379 }
380 SkASSERT(x < w);
381 SkASSERT(y < h);
Brian Osman788b9162020-02-07 10:36:46 -0500382 distanceMap[y * w + x] = std::max(distanceMap[y * w + x], (uint8_t) byteCoverage);
caryclark64022c12016-05-27 05:13:26 -0700383}
384
385static void filter_coverage(const uint8_t* map, int len, uint8_t min, uint8_t max,
386 uint8_t* filter) {
387 for (int index = 0; index < len; ++index) {
388 uint8_t in = map[index];
389 filter[index] = in < min ? 0 : max < in ? 0 : in;
390 }
391}
392
393static void construct_path(SkPath& path) {
394 path.reset();
395 path.moveTo(442, 101.5f);
396 path.quadTo(413.5f, 691, 772, 514);
397 path.lineTo(346, 721.5f);
398 path.lineTo(154, 209);
399 path.lineTo(442, 101.5f);
400 path.close();
401}
402
403struct ButtonPaints {
404 static const int kMaxStateCount = 3;
405 SkPaint fDisabled;
406 SkPaint fStates[kMaxStateCount];
Mike Reedb579f072019-01-03 15:45:53 -0500407 SkFont fLabelFont;
caryclark64022c12016-05-27 05:13:26 -0700408
409 ButtonPaints() {
410 fStates[0].setAntiAlias(true);
411 fStates[0].setStyle(SkPaint::kStroke_Style);
412 fStates[0].setColor(0xFF3F0000);
413 fStates[1] = fStates[0];
414 fStates[1].setStrokeWidth(3);
415 fStates[2] = fStates[1];
416 fStates[2].setColor(0xFFcf0000);
Mike Reedb579f072019-01-03 15:45:53 -0500417 fLabelFont.setSize(25.0f);
caryclark64022c12016-05-27 05:13:26 -0700418 }
419};
420
421struct Button {
422 SkRect fBounds;
423 int fStateCount;
424 int fState;
425 char fLabel;
426 bool fVisible;
427
428 Button(char label) {
429 fStateCount = 2;
430 fState = 0;
431 fLabel = label;
432 fVisible = false;
433 }
434
435 Button(char label, int stateCount) {
436 SkASSERT(stateCount <= ButtonPaints::kMaxStateCount);
437 fStateCount = stateCount;
438 fState = 0;
439 fLabel = label;
440 fVisible = false;
441 }
442
443 bool contains(const SkRect& rect) {
444 return fVisible && fBounds.contains(rect);
445 }
446
447 bool enabled() {
448 return SkToBool(fState);
449 }
450
451 void draw(SkCanvas* canvas, const ButtonPaints& paints) {
452 if (!fVisible) {
453 return;
454 }
455 canvas->drawRect(fBounds, paints.fStates[fState]);
Ben Wagner51e15a62019-05-07 15:38:46 -0400456 SkTextUtils::Draw(canvas, &fLabel, 1, SkTextEncoding::kUTF8, fBounds.centerX(), fBounds.fBottom - 5,
Mike Reedb579f072019-01-03 15:45:53 -0500457 paints.fLabelFont, SkPaint(), SkTextUtils::kCenter_Align);
caryclark64022c12016-05-27 05:13:26 -0700458 }
459
460 void toggle() {
461 if (++fState == fStateCount) {
462 fState = 0;
463 }
464 }
465
466 void setEnabled(bool enabled) {
467 fState = (int) enabled;
468 }
469};
470
471struct ControlPaints {
472 SkPaint fOutline;
473 SkPaint fIndicator;
474 SkPaint fFill;
475 SkPaint fLabel;
476 SkPaint fValue;
477
Mike Reed89126e42019-01-03 12:59:14 -0500478 SkFont fLabelFont;
479 SkFont fValueFont;
480
caryclark64022c12016-05-27 05:13:26 -0700481 ControlPaints() {
482 fOutline.setAntiAlias(true);
483 fOutline.setStyle(SkPaint::kStroke_Style);
484 fIndicator = fOutline;
485 fIndicator.setColor(SK_ColorRED);
486 fFill.setAntiAlias(true);
487 fFill.setColor(0x7fff0000);
488 fLabel.setAntiAlias(true);
Mike Reed89126e42019-01-03 12:59:14 -0500489 fLabelFont.setSize(13.0f);
caryclark64022c12016-05-27 05:13:26 -0700490 fValue.setAntiAlias(true);
Mike Reed89126e42019-01-03 12:59:14 -0500491 fValueFont.setSize(11.0f);
caryclark64022c12016-05-27 05:13:26 -0700492 }
493};
494
495struct UniControl {
496 SkString fName;
497 SkRect fBounds;
498 SkScalar fMin;
499 SkScalar fMax;
500 SkScalar fValLo;
501 SkScalar fYLo;
502 bool fVisible;
503
504 UniControl(const char* name, SkScalar min, SkScalar max) {
505 fName = name;
506 fValLo = fMin = min;
507 fMax = max;
508 fVisible = false;
509
510 }
511
512 virtual ~UniControl() {}
513
514 bool contains(const SkRect& rect) {
515 return fVisible && fBounds.contains(rect);
516 }
517
518 virtual void draw(SkCanvas* canvas, const ControlPaints& paints) {
519 if (!fVisible) {
520 return;
521 }
522 canvas->drawRect(fBounds, paints.fOutline);
523 fYLo = fBounds.fTop + (fValLo - fMin) * fBounds.height() / (fMax - fMin);
524 canvas->drawLine(fBounds.fLeft - 5, fYLo, fBounds.fRight + 5, fYLo, paints.fIndicator);
525 SkString label;
526 label.printf("%0.3g", fValLo);
Hal Canary89a644b2019-01-07 09:36:09 -0500527 canvas->drawString(label, fBounds.fLeft + 5, fYLo - 5, paints.fValueFont, paints.fValue);
528 canvas->drawString(fName, fBounds.fLeft, fBounds.bottom() + 11, paints.fLabelFont,
529 paints.fLabel);
caryclark64022c12016-05-27 05:13:26 -0700530 }
531};
532
533struct BiControl : public UniControl {
534 SkScalar fValHi;
535
Ben Wagner63fd7602017-10-09 15:45:33 -0400536 BiControl(const char* name, SkScalar min, SkScalar max)
caryclark64022c12016-05-27 05:13:26 -0700537 : UniControl(name, min, max)
538 , fValHi(fMax) {
539 }
Ben Wagner63fd7602017-10-09 15:45:33 -0400540
caryclark64022c12016-05-27 05:13:26 -0700541 virtual ~BiControl() {}
542
543 virtual void draw(SkCanvas* canvas, const ControlPaints& paints) {
544 UniControl::draw(canvas, paints);
545 if (!fVisible || fValHi == fValLo) {
546 return;
547 }
548 SkScalar yPos = fBounds.fTop + (fValHi - fMin) * fBounds.height() / (fMax - fMin);
549 canvas->drawLine(fBounds.fLeft - 5, yPos, fBounds.fRight + 5, yPos, paints.fIndicator);
550 SkString label;
551 label.printf("%0.3g", fValHi);
552 if (yPos < fYLo + 10) {
553 yPos = fYLo + 10;
554 }
Hal Canary4484b8f2019-01-08 14:00:08 -0500555 canvas->drawString(label, fBounds.fLeft + 5, yPos - 5, paints.fValueFont, paints.fValue);
caryclark64022c12016-05-27 05:13:26 -0700556 SkRect fill = { fBounds.fLeft, fYLo, fBounds.fRight, yPos };
557 canvas->drawRect(fill, paints.fFill);
558 }
559};
560
561
Ben Wagnerb2c4ea62018-08-08 11:36:17 -0400562class MyClick : public Sample::Click {
caryclark64022c12016-05-27 05:13:26 -0700563public:
564 enum ClickType {
565 kInvalidType = -1,
566 kPtType,
567 kVerbType,
568 kControlType,
569 kPathType,
570 } fType;
571
572 enum ControlType {
573 kInvalidControl = -1,
574 kFirstControl,
575 kFilterControl = kFirstControl,
576 kResControl,
577 kWeightControl,
578 kWidthControl,
579 kLastControl = kWidthControl,
580 kFirstButton,
581 kCubicButton = kFirstButton,
582 kConicButton,
583 kQuadButton,
584 kLineButton,
585 kLastVerbButton = kLineButton,
586 kAddButton,
587 kDeleteButton,
588 kInOutButton,
589 kFillButton,
590 kSkeletonButton,
591 kFilterButton,
592 kBisectButton,
593 kJoinButton,
594 kLastButton = kJoinButton,
595 kPathMove,
596 } fControl;
597
598 SkPath::Verb fVerb;
599 SkScalar fWeight;
600
Hal Canaryfcf63592019-07-12 11:32:43 -0400601 MyClick(ClickType type, ControlType control)
602 : fType(type)
caryclark64022c12016-05-27 05:13:26 -0700603 , fControl(control)
604 , fVerb((SkPath::Verb) -1)
605 , fWeight(1) {
606 }
607
Hal Canaryfcf63592019-07-12 11:32:43 -0400608 MyClick(ClickType type, int index)
609 : fType(type)
caryclark64022c12016-05-27 05:13:26 -0700610 , fControl((ControlType) index)
611 , fVerb((SkPath::Verb) -1)
612 , fWeight(1) {
613 }
614
Hal Canaryfcf63592019-07-12 11:32:43 -0400615 MyClick(ClickType type, int index, SkPath::Verb verb, SkScalar weight)
616 : fType(type)
caryclark64022c12016-05-27 05:13:26 -0700617 , fControl((ControlType) index)
618 , fVerb(verb)
619 , fWeight(weight) {
620 }
621
622 bool isButton() {
623 return kFirstButton <= fControl && fControl <= kLastButton;
624 }
625
626 int ptHit() const {
627 SkASSERT(fType == kPtType);
628 return (int) fControl;
629 }
630
631 int verbHit() const {
632 SkASSERT(fType == kVerbType);
633 return (int) fControl;
634 }
635};
636
637enum {
638 kControlCount = MyClick::kLastControl - MyClick::kFirstControl + 1,
639};
640
641static struct ControlPair {
642 UniControl* fControl;
643 MyClick::ControlType fControlType;
644} kControlList[kControlCount];
645
646enum {
647 kButtonCount = MyClick::kLastButton - MyClick::kFirstButton + 1,
648 kVerbCount = MyClick::kLastVerbButton - MyClick::kFirstButton + 1,
649};
650
651static struct ButtonPair {
652 Button* fButton;
653 MyClick::ControlType fButtonType;
654} kButtonList[kButtonCount];
655
656static void enable_verb_button(MyClick::ControlType type) {
657 for (int index = 0; index < kButtonCount; ++index) {
658 MyClick::ControlType testType = kButtonList[index].fButtonType;
659 if (MyClick::kFirstButton <= testType && testType <= MyClick::kLastVerbButton) {
660 Button* button = kButtonList[index].fButton;
661 button->setEnabled(testType == type);
662 }
663 }
664}
665
666struct Stroke;
667
668struct Active {
669 Active* fNext;
670 Stroke* fParent;
671 SkScalar fStart;
672 SkScalar fEnd;
673
674 void reset() {
Ben Wagnera93a14a2017-08-28 10:34:05 -0400675 fNext = nullptr;
caryclark64022c12016-05-27 05:13:26 -0700676 fStart = 0;
677 fEnd = 1;
678 }
679};
680
681struct Stroke {
682 SkPath fPath;
683 Active fActive;
684 bool fInner;
685
686 void reset() {
687 fPath.reset();
688 fActive.reset();
689 }
690};
691
692struct PathUndo {
693 SkPath fPath;
Hal Canary8918d532019-07-12 10:04:14 -0400694 std::unique_ptr<PathUndo> fNext;
caryclark64022c12016-05-27 05:13:26 -0700695};
696
Ben Wagnerb2c4ea62018-08-08 11:36:17 -0400697class AAGeometryView : public Sample {
caryclark64022c12016-05-27 05:13:26 -0700698 SkPaint fActivePaint;
699 SkPaint fComplexPaint;
700 SkPaint fCoveragePaint;
Mike Reedb579f072019-01-03 15:45:53 -0500701 SkFont fLegendLeftFont;
702 SkFont fLegendRightFont;
caryclark64022c12016-05-27 05:13:26 -0700703 SkPaint fPointPaint;
704 SkPaint fSkeletonPaint;
705 SkPaint fLightSkeletonPaint;
706 SkPath fPath;
707 ControlPaints fControlPaints;
708 UniControl fResControl;
709 UniControl fWeightControl;
710 UniControl fWidthControl;
711 BiControl fFilterControl;
712 ButtonPaints fButtonPaints;
713 Button fCubicButton;
714 Button fConicButton;
715 Button fQuadButton;
716 Button fLineButton;
717 Button fAddButton;
718 Button fDeleteButton;
719 Button fFillButton;
720 Button fSkeletonButton;
721 Button fFilterButton;
722 Button fBisectButton;
723 Button fJoinButton;
724 Button fInOutButton;
725 SkTArray<Stroke> fStrokes;
Hal Canary8918d532019-07-12 10:04:14 -0400726 std::unique_ptr<PathUndo> fUndo;
caryclark64022c12016-05-27 05:13:26 -0700727 int fActivePt;
728 int fActiveVerb;
729 bool fHandlePathMove;
730 bool fShowLegend;
731 bool fHideAll;
Chris Dalton08d1a252017-10-20 11:46:47 -0600732 const int kHitToleranace = 25;
caryclark64022c12016-05-27 05:13:26 -0700733
734public:
735
Ben Wagner63fd7602017-10-09 15:45:33 -0400736 AAGeometryView()
caryclark64022c12016-05-27 05:13:26 -0700737 : fResControl("error", 0, 10)
738 , fWeightControl("weight", 0, 5)
739 , fWidthControl("width", FLT_EPSILON, 100)
740 , fFilterControl("filter", 0, 255)
741 , fCubicButton('C')
742 , fConicButton('K')
743 , fQuadButton('Q')
744 , fLineButton('L')
745 , fAddButton('+')
746 , fDeleteButton('x')
747 , fFillButton('p')
748 , fSkeletonButton('s')
749 , fFilterButton('f', 3)
750 , fBisectButton('b')
751 , fJoinButton('j')
752 , fInOutButton('|')
caryclark64022c12016-05-27 05:13:26 -0700753 , fActivePt(-1)
754 , fActiveVerb(-1)
755 , fHandlePathMove(true)
756 , fShowLegend(false)
757 , fHideAll(false)
758 {
759 fCoveragePaint.setAntiAlias(true);
760 fCoveragePaint.setColor(SK_ColorBLUE);
761 SkPaint strokePaint;
762 strokePaint.setAntiAlias(true);
763 strokePaint.setStyle(SkPaint::kStroke_Style);
764 fPointPaint = strokePaint;
765 fPointPaint.setColor(0x99ee3300);
766 fSkeletonPaint = strokePaint;
767 fSkeletonPaint.setColor(SK_ColorRED);
768 fLightSkeletonPaint = fSkeletonPaint;
769 fLightSkeletonPaint.setColor(0xFFFF7f7f);
770 fActivePaint = strokePaint;
771 fActivePaint.setColor(0x99ee3300);
772 fActivePaint.setStrokeWidth(5);
773 fComplexPaint = fActivePaint;
774 fComplexPaint.setColor(SK_ColorBLUE);
Mike Reedb579f072019-01-03 15:45:53 -0500775 fLegendLeftFont.setSize(13);
776 fLegendRightFont = fLegendLeftFont;
caryclark64022c12016-05-27 05:13:26 -0700777 construct_path(fPath);
778 fFillButton.fVisible = fSkeletonButton.fVisible = fFilterButton.fVisible
779 = fBisectButton.fVisible = fJoinButton.fVisible = fInOutButton.fVisible = true;
780 fSkeletonButton.setEnabled(true);
781 fInOutButton.setEnabled(true);
782 fJoinButton.setEnabled(true);
783 fFilterControl.fValLo = 120;
784 fFilterControl.fValHi = 141;
785 fFilterControl.fVisible = fFilterButton.fState == 2;
786 fResControl.fValLo = 5;
787 fResControl.fVisible = true;
788 fWidthControl.fValLo = 50;
789 fWidthControl.fVisible = true;
790 init_controlList();
791 init_buttonList();
792 }
793
Hal Canary8918d532019-07-12 10:04:14 -0400794 ~AAGeometryView() override {
795 // Free linked list without deep recursion.
796 std::unique_ptr<PathUndo> undo = std::move(fUndo);
797 while (undo) {
798 undo = std::move(undo->fNext);
799 }
800 }
801
caryclark64022c12016-05-27 05:13:26 -0700802 bool constructPath() {
803 construct_path(fPath);
caryclark64022c12016-05-27 05:13:26 -0700804 return true;
805 }
806
Hal Canaryb1f411a2019-08-29 10:39:22 -0400807 void savePath(skui::InputState state) {
808 if (state != skui::InputState::kDown) {
caryclark64022c12016-05-27 05:13:26 -0700809 return;
810 }
811 if (fUndo && fUndo->fPath == fPath) {
812 return;
813 }
Hal Canary8918d532019-07-12 10:04:14 -0400814 std::unique_ptr<PathUndo> undo(new PathUndo);
caryclark64022c12016-05-27 05:13:26 -0700815 undo->fPath = fPath;
Hal Canary8918d532019-07-12 10:04:14 -0400816 undo->fNext = std::move(fUndo);
817 fUndo = std::move(undo);
caryclark64022c12016-05-27 05:13:26 -0700818 }
819
820 bool undo() {
821 if (!fUndo) {
822 return false;
823 }
Hal Canary8918d532019-07-12 10:04:14 -0400824 fPath = std::move(fUndo->fPath);
825 fUndo = std::move(fUndo->fNext);
caryclark64022c12016-05-27 05:13:26 -0700826 validatePath();
caryclark64022c12016-05-27 05:13:26 -0700827 return true;
828 }
829
Hal Canary8918d532019-07-12 10:04:14 -0400830 void validatePath() {}
caryclark64022c12016-05-27 05:13:26 -0700831
832 void set_controlList(int index, UniControl* control, MyClick::ControlType type) {
833 kControlList[index].fControl = control;
834 kControlList[index].fControlType = type;
835 }
836
837 #define SET_CONTROL(Name) set_controlList(index++, &f##Name##Control, \
838 MyClick::k##Name##Control)
839
840 bool hideAll() {
841 fHideAll ^= true;
caryclark64022c12016-05-27 05:13:26 -0700842 return true;
843 }
844
845 void init_controlList() {
846 int index = 0;
847 SET_CONTROL(Width);
848 SET_CONTROL(Res);
849 SET_CONTROL(Filter);
850 SET_CONTROL(Weight);
Brian Osman16adfa32016-10-18 14:42:44 -0400851 }
Ben Wagner63fd7602017-10-09 15:45:33 -0400852
caryclark64022c12016-05-27 05:13:26 -0700853 #undef SET_CONTROL
854
855 void set_buttonList(int index, Button* button, MyClick::ControlType type) {
856 kButtonList[index].fButton = button;
857 kButtonList[index].fButtonType = type;
858 }
859
860 #define SET_BUTTON(Name) set_buttonList(index++, &f##Name##Button, \
861 MyClick::k##Name##Button)
862
863 void init_buttonList() {
864 int index = 0;
865 SET_BUTTON(Fill);
866 SET_BUTTON(Skeleton);
867 SET_BUTTON(Filter);
868 SET_BUTTON(Bisect);
869 SET_BUTTON(Join);
870 SET_BUTTON(InOut);
871 SET_BUTTON(Cubic);
872 SET_BUTTON(Conic);
873 SET_BUTTON(Quad);
874 SET_BUTTON(Line);
875 SET_BUTTON(Add);
876 SET_BUTTON(Delete);
877 }
878
879 #undef SET_BUTTON
880
Hal Canary8a027312019-07-03 10:55:44 -0400881 SkString name() override { return SkString("AAGeometry"); }
882
Hal Canary6cc65e12019-07-03 15:53:04 -0400883 bool onChar(SkUnichar) override;
Ben Wagner63fd7602017-10-09 15:45:33 -0400884
caryclark64022c12016-05-27 05:13:26 -0700885 void onSizeChange() override {
886 setControlButtonsPos();
887 this->INHERITED::onSizeChange();
888 }
889
890 bool pathDump() {
891 fPath.dump();
892 return true;
893 }
894
895 bool scaleDown() {
896 SkMatrix matrix;
897 SkRect bounds = fPath.getBounds();
898 matrix.setScale(1.f / 1.5f, 1.f / 1.5f, bounds.centerX(), bounds.centerY());
899 fPath.transform(matrix);
900 validatePath();
caryclark64022c12016-05-27 05:13:26 -0700901 return true;
902 }
903
904 bool scaleToFit() {
905 SkMatrix matrix;
906 SkRect bounds = fPath.getBounds();
Brian Osman788b9162020-02-07 10:36:46 -0500907 SkScalar scale = std::min(this->width() / bounds.width(), this->height() / bounds.height())
caryclark64022c12016-05-27 05:13:26 -0700908 * 0.8f;
909 matrix.setScale(scale, scale, bounds.centerX(), bounds.centerY());
910 fPath.transform(matrix);
911 bounds = fPath.getBounds();
912 SkScalar offsetX = (this->width() - bounds.width()) / 2 - bounds.fLeft;
913 SkScalar offsetY = (this->height() - bounds.height()) / 2 - bounds.fTop;
914 fPath.offset(offsetX, offsetY);
915 validatePath();
caryclark64022c12016-05-27 05:13:26 -0700916 return true;
917 }
918
919 bool scaleUp() {
920 SkMatrix matrix;
921 SkRect bounds = fPath.getBounds();
922 matrix.setScale(1.5f, 1.5f, bounds.centerX(), bounds.centerY());
923 fPath.transform(matrix);
924 validatePath();
caryclark64022c12016-05-27 05:13:26 -0700925 return true;
926 }
927
928 void setControlButtonsPos() {
929 SkScalar widthOffset = this->width() - 100;
930 for (int index = 0; index < kControlCount; ++index) {
931 if (kControlList[index].fControl->fVisible) {
932 kControlList[index].fControl->fBounds.setXYWH(widthOffset, 30, 30, 400);
933 widthOffset -= 50;
934 }
935 }
936 SkScalar buttonOffset = 0;
937 for (int index = 0; index < kButtonCount; ++index) {
938 kButtonList[index].fButton->fBounds.setXYWH(this->width() - 50,
939 buttonOffset += 50, 30, 30);
940 }
941 }
942
943 bool showLegend() {
944 fShowLegend ^= true;
caryclark64022c12016-05-27 05:13:26 -0700945 return true;
946 }
947
948 void draw_bisect(SkCanvas* canvas, const SkVector& lastVector, const SkVector& vector,
949 const SkPoint& pt) {
950 SkVector lastV = lastVector;
951 SkScalar lastLen = lastVector.length();
952 SkVector nextV = vector;
953 SkScalar nextLen = vector.length();
954 if (lastLen < nextLen) {
955 lastV.setLength(nextLen);
956 } else {
957 nextV.setLength(lastLen);
958 }
959
960 SkVector bisect = { (lastV.fX + nextV.fX) / 2, (lastV.fY + nextV.fY) / 2 };
961 bisect.setLength(fWidthControl.fValLo * 2);
962 if (fBisectButton.enabled()) {
Hal Canary23e474c2017-05-15 13:35:35 -0400963 canvas->drawLine(pt, pt + bisect, fSkeletonPaint);
caryclark64022c12016-05-27 05:13:26 -0700964 }
965 lastV.setLength(fWidthControl.fValLo);
966 if (fBisectButton.enabled()) {
Hal Canary23e474c2017-05-15 13:35:35 -0400967 canvas->drawLine(pt, {pt.fX - lastV.fY, pt.fY + lastV.fX}, fSkeletonPaint);
caryclark64022c12016-05-27 05:13:26 -0700968 }
969 nextV.setLength(fWidthControl.fValLo);
970 if (fBisectButton.enabled()) {
Hal Canary23e474c2017-05-15 13:35:35 -0400971 canvas->drawLine(pt, {pt.fX + nextV.fY, pt.fY - nextV.fX}, fSkeletonPaint);
caryclark64022c12016-05-27 05:13:26 -0700972 }
973 if (fJoinButton.enabled()) {
974 SkScalar r = fWidthControl.fValLo;
975 SkRect oval = { pt.fX - r, pt.fY - r, pt.fX + r, pt.fY + r};
976 SkScalar startAngle = SkScalarATan2(lastV.fX, -lastV.fY) * 180.f / SK_ScalarPI;
977 SkScalar endAngle = SkScalarATan2(-nextV.fX, nextV.fY) * 180.f / SK_ScalarPI;
978 if (endAngle > startAngle) {
979 canvas->drawArc(oval, startAngle, endAngle - startAngle, false, fSkeletonPaint);
980 } else {
981 canvas->drawArc(oval, startAngle, 360 - (startAngle - endAngle), false,
982 fSkeletonPaint);
983 }
984 }
985 }
986
987 void draw_bisects(SkCanvas* canvas, bool activeOnly) {
988 SkVector firstVector, lastVector, nextLast, vector;
989 SkPoint pts[4];
990 SkPoint firstPt = { 0, 0 }; // init to avoid warning;
991 SkPath::Verb verb;
992 SkPath::Iter iter(fPath, true);
993 bool foundFirst = false;
994 int counter = -1;
995 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
996 ++counter;
997 if (activeOnly && counter != fActiveVerb && counter - 1 != fActiveVerb
998 && counter + 1 != fActiveVerb
999 && (fActiveVerb != 1 || counter != fPath.countVerbs())) {
1000 continue;
1001 }
1002 switch (verb) {
1003 case SkPath::kLine_Verb:
1004 nextLast = pts[0] - pts[1];
1005 vector = pts[1] - pts[0];
1006 break;
1007 case SkPath::kQuad_Verb: {
1008 nextLast = pts[1] - pts[2];
1009 if (SkScalarNearlyZero(nextLast.length())) {
1010 nextLast = pts[0] - pts[2];
1011 }
1012 vector = pts[1] - pts[0];
1013 if (SkScalarNearlyZero(vector.length())) {
1014 vector = pts[2] - pts[0];
1015 }
1016 if (!fBisectButton.enabled()) {
1017 break;
1018 }
1019 SkScalar t = SkFindQuadMaxCurvature(pts);
1020 if (0 < t && t < 1) {
1021 SkPoint maxPt = SkEvalQuadAt(pts, t);
1022 SkVector tangent = SkEvalQuadTangentAt(pts, t);
1023 tangent.setLength(fWidthControl.fValLo * 2);
Hal Canary23e474c2017-05-15 13:35:35 -04001024 canvas->drawLine(maxPt, {maxPt.fX + tangent.fY, maxPt.fY - tangent.fX},
1025 fSkeletonPaint);
caryclark64022c12016-05-27 05:13:26 -07001026 }
1027 } break;
1028 case SkPath::kConic_Verb:
1029 nextLast = pts[1] - pts[2];
1030 if (SkScalarNearlyZero(nextLast.length())) {
1031 nextLast = pts[0] - pts[2];
1032 }
1033 vector = pts[1] - pts[0];
1034 if (SkScalarNearlyZero(vector.length())) {
1035 vector = pts[2] - pts[0];
1036 }
1037 if (!fBisectButton.enabled()) {
1038 break;
1039 }
1040 // FIXME : need max curvature or equivalent here
1041 break;
1042 case SkPath::kCubic_Verb: {
1043 nextLast = pts[2] - pts[3];
1044 if (SkScalarNearlyZero(nextLast.length())) {
1045 nextLast = pts[1] - pts[3];
1046 if (SkScalarNearlyZero(nextLast.length())) {
1047 nextLast = pts[0] - pts[3];
1048 }
1049 }
1050 vector = pts[0] - pts[1];
1051 if (SkScalarNearlyZero(vector.length())) {
1052 vector = pts[0] - pts[2];
1053 if (SkScalarNearlyZero(vector.length())) {
1054 vector = pts[0] - pts[3];
1055 }
1056 }
1057 if (!fBisectButton.enabled()) {
1058 break;
1059 }
1060 SkScalar tMax[2];
1061 int tMaxCount = SkFindCubicMaxCurvature(pts, tMax);
1062 for (int tIndex = 0; tIndex < tMaxCount; ++tIndex) {
1063 if (0 >= tMax[tIndex] || tMax[tIndex] >= 1) {
1064 continue;
1065 }
1066 SkPoint maxPt;
1067 SkVector tangent;
Ben Wagnera93a14a2017-08-28 10:34:05 -04001068 SkEvalCubicAt(pts, tMax[tIndex], &maxPt, &tangent, nullptr);
caryclark64022c12016-05-27 05:13:26 -07001069 tangent.setLength(fWidthControl.fValLo * 2);
Hal Canary23e474c2017-05-15 13:35:35 -04001070 canvas->drawLine(maxPt, {maxPt.fX + tangent.fY, maxPt.fY - tangent.fX},
1071 fSkeletonPaint);
caryclark64022c12016-05-27 05:13:26 -07001072 }
1073 } break;
1074 case SkPath::kClose_Verb:
1075 if (foundFirst) {
1076 draw_bisect(canvas, lastVector, firstVector, firstPt);
1077 foundFirst = false;
1078 }
1079 break;
Ben Wagner63fd7602017-10-09 15:45:33 -04001080 default:
caryclark64022c12016-05-27 05:13:26 -07001081 break;
1082 }
1083 if (SkPath::kLine_Verb <= verb && verb <= SkPath::kCubic_Verb) {
1084 if (!foundFirst) {
1085 firstPt = pts[0];
1086 firstVector = vector;
1087 foundFirst = true;
1088 } else {
1089 draw_bisect(canvas, lastVector, vector, pts[0]);
1090 }
1091 lastVector = nextLast;
1092 }
1093 }
1094 }
1095
1096 void draw_legend(SkCanvas* canvas);
1097
1098 void draw_segment(SkCanvas* canvas) {
1099 SkPoint pts[4];
1100 SkPath::Verb verb;
1101 SkPath::Iter iter(fPath, true);
1102 int counter = -1;
1103 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1104 if (++counter < fActiveVerb) {
1105 continue;
1106 }
1107 switch (verb) {
1108 case SkPath::kLine_Verb:
1109 canvas->drawPoints(SkCanvas::kLines_PointMode, 2, pts, fActivePaint);
1110 draw_points(canvas, pts, 2);
1111 break;
1112 case SkPath::kQuad_Verb: {
1113 SkPath qPath;
1114 qPath.moveTo(pts[0]);
1115 qPath.quadTo(pts[1], pts[2]);
1116 canvas->drawPath(qPath, fActivePaint);
1117 draw_points(canvas, pts, 3);
1118 } break;
1119 case SkPath::kConic_Verb: {
1120 SkPath conicPath;
1121 conicPath.moveTo(pts[0]);
1122 conicPath.conicTo(pts[1], pts[2], iter.conicWeight());
1123 canvas->drawPath(conicPath, fActivePaint);
1124 draw_points(canvas, pts, 3);
1125 } break;
1126 case SkPath::kCubic_Verb: {
Cary Clark7eb01e02016-12-08 14:36:32 -05001127 SkScalar loopT[3];
1128 int complex = SkDCubic::ComplexBreak(pts, loopT);
caryclark64022c12016-05-27 05:13:26 -07001129 SkPath cPath;
1130 cPath.moveTo(pts[0]);
1131 cPath.cubicTo(pts[1], pts[2], pts[3]);
1132 canvas->drawPath(cPath, complex ? fComplexPaint : fActivePaint);
1133 draw_points(canvas, pts, 4);
1134 } break;
Ben Wagner63fd7602017-10-09 15:45:33 -04001135 default:
caryclark64022c12016-05-27 05:13:26 -07001136 break;
1137 }
1138 return;
1139 }
1140 }
1141
1142 void draw_points(SkCanvas* canvas, SkPoint* points, int count) {
1143 for (int index = 0; index < count; ++index) {
1144 canvas->drawCircle(points[index].fX, points[index].fY, 10, fPointPaint);
1145 }
1146 }
1147
1148 int hittest_verb(SkPoint pt, SkPath::Verb* verbPtr, SkScalar* weight) {
1149 SkIntersections i;
1150 SkDLine hHit = {{{pt.fX - kHitToleranace, pt.fY }, {pt.fX + kHitToleranace, pt.fY}}};
1151 SkDLine vHit = {{{pt.fX, pt.fY - kHitToleranace }, {pt.fX, pt.fY + kHitToleranace}}};
1152 SkPoint pts[4];
1153 SkPath::Verb verb;
1154 SkPath::Iter iter(fPath, true);
1155 int counter = -1;
1156 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1157 ++counter;
1158 switch (verb) {
1159 case SkPath::kLine_Verb: {
1160 SkDLine line;
1161 line.set(pts);
1162 if (i.intersect(line, hHit) || i.intersect(line, vHit)) {
1163 *verbPtr = verb;
1164 *weight = 1;
1165 return counter;
1166 }
1167 } break;
1168 case SkPath::kQuad_Verb: {
1169 SkDQuad quad;
1170 quad.set(pts);
1171 if (i.intersect(quad, hHit) || i.intersect(quad, vHit)) {
1172 *verbPtr = verb;
1173 *weight = 1;
1174 return counter;
1175 }
1176 } break;
1177 case SkPath::kConic_Verb: {
1178 SkDConic conic;
1179 SkScalar w = iter.conicWeight();
1180 conic.set(pts, w);
1181 if (i.intersect(conic, hHit) || i.intersect(conic, vHit)) {
1182 *verbPtr = verb;
1183 *weight = w;
1184 return counter;
1185 }
1186 } break;
1187 case SkPath::kCubic_Verb: {
1188 SkDCubic cubic;
1189 cubic.set(pts);
1190 if (i.intersect(cubic, hHit) || i.intersect(cubic, vHit)) {
1191 *verbPtr = verb;
1192 *weight = 1;
1193 return counter;
1194 }
1195 } break;
Ben Wagner63fd7602017-10-09 15:45:33 -04001196 default:
caryclark64022c12016-05-27 05:13:26 -07001197 break;
1198 }
1199 }
1200 return -1;
1201 }
1202
1203 SkScalar pt_to_line(SkPoint s, SkPoint e, int x, int y) {
1204 SkScalar radius = fWidthControl.fValLo;
1205 SkVector adjOpp = e - s;
Cary Clarkdf429f32017-11-08 11:44:31 -05001206 SkScalar lenSq = SkPointPriv::LengthSqd(adjOpp);
caryclark64022c12016-05-27 05:13:26 -07001207 SkPoint rotated = {
1208 (y - s.fY) * adjOpp.fY + (x - s.fX) * adjOpp.fX,
1209 (y - s.fY) * adjOpp.fX - (x - s.fX) * adjOpp.fY,
1210 };
1211 if (rotated.fX < 0 || rotated.fX > lenSq) {
1212 return -radius;
1213 }
1214 rotated.fY /= SkScalarSqrt(lenSq);
Brian Osman788b9162020-02-07 10:36:46 -05001215 return std::max(-radius, std::min(radius, rotated.fY));
caryclark64022c12016-05-27 05:13:26 -07001216 }
1217
1218 // given a line, compute the interior and exterior gradient coverage
1219 bool coverage(SkPoint s, SkPoint e, uint8_t* distanceMap, int w, int h) {
1220 SkScalar radius = fWidthControl.fValLo;
Brian Osman788b9162020-02-07 10:36:46 -05001221 int minX = std::max(0, (int) (std::min(s.fX, e.fX) - radius));
1222 int minY = std::max(0, (int) (std::min(s.fY, e.fY) - radius));
1223 int maxX = std::min(w, (int) (std::max(s.fX, e.fX) + radius) + 1);
1224 int maxY = std::min(h, (int) (std::max(s.fY, e.fY) + radius) + 1);
caryclark64022c12016-05-27 05:13:26 -07001225 for (int y = minY; y < maxY; ++y) {
1226 for (int x = minX; x < maxX; ++x) {
1227 SkScalar ptToLineDist = pt_to_line(s, e, x, y);
1228 if (ptToLineDist > -radius && ptToLineDist < radius) {
1229 SkScalar coverage = ptToLineDist / radius;
1230 add_to_map(1 - SkScalarAbs(coverage), x, y, distanceMap, w, h);
1231 }
1232 SkVector ptToS = { x - s.fX, y - s.fY };
1233 SkScalar dist = ptToS.length();
1234 if (dist < radius) {
1235 SkScalar coverage = dist / radius;
1236 add_to_map(1 - SkScalarAbs(coverage), x, y, distanceMap, w, h);
1237 }
1238 SkVector ptToE = { x - e.fX, y - e.fY };
1239 dist = ptToE.length();
1240 if (dist < radius) {
1241 SkScalar coverage = dist / radius;
1242 add_to_map(1 - SkScalarAbs(coverage), x, y, distanceMap, w, h);
1243 }
1244 }
1245 }
1246 return true;
1247 }
1248
1249 void quad_coverage(SkPoint pts[3], uint8_t* distanceMap, int w, int h) {
1250 SkScalar dist = pts[0].Distance(pts[0], pts[2]);
1251 if (dist < gCurveDistance) {
1252 (void) coverage(pts[0], pts[2], distanceMap, w, h);
1253 return;
1254 }
1255 SkPoint split[5];
1256 SkChopQuadAt(pts, split, 0.5f);
1257 quad_coverage(&split[0], distanceMap, w, h);
1258 quad_coverage(&split[2], distanceMap, w, h);
1259 }
1260
1261 void conic_coverage(SkPoint pts[3], SkScalar weight, uint8_t* distanceMap, int w, int h) {
1262 SkScalar dist = pts[0].Distance(pts[0], pts[2]);
1263 if (dist < gCurveDistance) {
1264 (void) coverage(pts[0], pts[2], distanceMap, w, h);
1265 return;
1266 }
1267 SkConic split[2];
1268 SkConic conic;
1269 conic.set(pts, weight);
caryclark414c4292016-09-26 11:03:54 -07001270 if (conic.chopAt(0.5f, split)) {
1271 conic_coverage(split[0].fPts, split[0].fW, distanceMap, w, h);
1272 conic_coverage(split[1].fPts, split[1].fW, distanceMap, w, h);
1273 }
caryclark64022c12016-05-27 05:13:26 -07001274 }
1275
1276 void cubic_coverage(SkPoint pts[4], uint8_t* distanceMap, int w, int h) {
1277 SkScalar dist = pts[0].Distance(pts[0], pts[3]);
1278 if (dist < gCurveDistance) {
1279 (void) coverage(pts[0], pts[3], distanceMap, w, h);
1280 return;
1281 }
1282 SkPoint split[7];
1283 SkChopCubicAt(pts, split, 0.5f);
1284 cubic_coverage(&split[0], distanceMap, w, h);
1285 cubic_coverage(&split[3], distanceMap, w, h);
1286 }
1287
1288 void path_coverage(const SkPath& path, uint8_t* distanceMap, int w, int h) {
1289 memset(distanceMap, 0, sizeof(distanceMap[0]) * w * h);
1290 SkPoint pts[4];
1291 SkPath::Verb verb;
1292 SkPath::Iter iter(path, true);
1293 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1294 switch (verb) {
1295 case SkPath::kLine_Verb:
1296 (void) coverage(pts[0], pts[1], distanceMap, w, h);
1297 break;
1298 case SkPath::kQuad_Verb:
1299 quad_coverage(pts, distanceMap, w, h);
1300 break;
1301 case SkPath::kConic_Verb:
1302 conic_coverage(pts, iter.conicWeight(), distanceMap, w, h);
1303 break;
1304 case SkPath::kCubic_Verb:
1305 cubic_coverage(pts, distanceMap, w, h);
1306 break;
Ben Wagner63fd7602017-10-09 15:45:33 -04001307 default:
caryclark64022c12016-05-27 05:13:26 -07001308 break;
1309 }
1310 }
1311 }
1312
1313 static uint8_t* set_up_dist_map(const SkImageInfo& imageInfo, SkBitmap* distMap) {
1314 distMap->setInfo(imageInfo);
1315 distMap->setIsVolatile(true);
1316 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; )