-
Notifications
You must be signed in to change notification settings - Fork 153
/
sketch.ts
executable file
·112 lines (90 loc) · 2.83 KB
/
sketch.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
// =============================================================================
// Boost.js | Drawing Component
// (c) Mathigon
// =============================================================================
import {EventTarget, last} from '@mathigon/core';
import {Point, Segment, isBetween, SimplePoint} from '@mathigon/fermat';
import {$N, slide, SVGParentView, SVGView, $body} from '@mathigon/boost';
interface SketchOptions {
noStart?: boolean;
intersect?: boolean;
paths?: SVGView;
}
export class Sketch extends EventTarget {
paths: SVGView[] = [];
p?: SimplePoint;
private activePath?: SVGView;
drawing = false;
constructor(private $svg: SVGParentView,
private readonly options: SketchOptions = {}) {
super();
$svg.addClass('drawing-pointer');
$svg.css('touch-action', 'none');
slide($svg, {
start: p => {
if (!this.options.noStart) this.start(p);
},
move: p => {
if (!this.drawing) return;
const box = $svg.viewBox;
if (!isBetween(p.x, 0, box.width) || !isBetween(p.y, 0, box.height))
return this.stop();
this.addPoint(p);
},
end: () => this.stop()
});
$body.on('scroll', () => this.stop());
}
start(p: SimplePoint) {
if (this.p && Point.distance(this.p, p) < 20) {
this.activePath!.addPoint(p);
} else {
this.trigger('start');
const $parent = this.options.paths || this.$svg;
this.activePath = $N('path', {class: 'drawing-path'}, $parent) as SVGView;
this.activePath.points = [p];
this.paths.push(this.activePath);
}
this.drawing = true;
this.p = p;
}
addPoint(p: SimplePoint) {
if (Point.distance(this.p!, p) > 3) {
this.activePath!.addPoint(p);
this.p = p;
this.checkForIntersects();
}
}
stop() {
if (this.drawing) this.trigger('end');
this.drawing = false;
}
clear() {
this.paths.forEach(path => { path.remove(); });
this.paths = [];
this.trigger('clear');
}
clearPaths($paths: SVGView[]) {
for (const $p of $paths) $p.exit('draw', 200);
this.paths = this.paths.filter(p => !$paths.includes(p));
}
checkForIntersects() {
if (!this.options.intersect || this.paths.length <= 1) return;
let path1 = last(this.paths);
let points1 = path1.points as Point[];
let line1 = new Segment(points1[points1.length - 2],
points1[points1.length - 1]);
for (let i = 0; i < this.paths.length - 1; ++i) {
let path2 = this.paths[i];
let points2 = path2.points as Point[];
for (let j = 1; j < points2.length - 2; ++j) {
let line2 = new Segment(points2[j], points2[j + 1]);
let t = Segment.intersect(line1, line2);
if (t) {
this.trigger('intersect', {point: t, paths: [path1, path2]});
return;
}
}
}
}
}