1
- import { createElement } from 'preact' ;
1
+ import { createElement , Fragment } from 'preact' ;
2
2
import { useRef , useEffect } from 'preact/hooks' ;
3
3
import propTypes from 'prop-types' ;
4
4
@@ -9,15 +9,18 @@ const Canvas = ({
9
9
handleMouseUp,
10
10
handleMouseMove,
11
11
handleMouseLeave,
12
- lines,
12
+ doodles,
13
+ handleDoodleClick,
13
14
} ) => {
14
15
const canvasRef = useRef ( null ) ;
16
+ const hitCanvasRef = useRef ( null ) ;
15
17
16
- const drawLine = ( ctx , line ) => {
18
+ const drawLine = ( ctx , hitctx , line , colorHash ) => {
17
19
if ( line . points . length <= 1 ) {
18
20
return ;
19
21
}
20
22
const [ [ startX , startY ] , ...rest ] = line . points ;
23
+ // Draw each line twice, once on the visible canvas, once on the 'hit' canvas
21
24
// Move to the first point, begin the line
22
25
ctx . beginPath ( ) ;
23
26
ctx . moveTo ( startX , startY ) ;
@@ -37,29 +40,113 @@ const Canvas = ({
37
40
38
41
ctx . stroke ( ) ;
39
42
ctx . closePath ( ) ;
43
+
44
+ // now draw the same line, but on our hit canvas
45
+ hitctx . beginPath ( ) ;
46
+ hitctx . moveTo ( startX , startY ) ;
47
+ hitctx . lineWidth = line . size ;
48
+
49
+ if ( line . tool === 'pen' ) {
50
+ hitctx . globalCompositeOperation = 'source-over' ;
51
+ hitctx . strokeStyle = colorHash ;
52
+ } else {
53
+ hitctx . globalCompositeOperation = 'destination-out' ;
54
+ }
55
+
56
+ // Draw the rest of the lines
57
+ for ( let [ x , y ] of rest ) {
58
+ hitctx . lineTo ( x , y ) ;
59
+ }
60
+
61
+ hitctx . stroke ( ) ;
62
+ hitctx . closePath ( ) ;
63
+ } ;
64
+
65
+ const colorToTag = ( r , g , b ) => {
66
+ const tagNum = ( ( r << 16 ) | ( g << 8 ) | b ) - 1 ;
67
+ return `t${ tagNum } ` ;
68
+ } ;
69
+
70
+ const tagToColor = tag => {
71
+ if ( tag === 'none' ) {
72
+ // this is an untagged (in progress) doodle
73
+ return '#000000' ;
74
+ }
75
+ const tagNum = parseInt ( tag . substring ( 1 ) , 10 ) + 1 ;
76
+ return `#${ tagNum . toString ( 16 ) . padStart ( 6 , '0' ) } ` ;
40
77
} ;
41
78
79
+ // have to add event listener this way because the canvas has pointerEvents = None
80
+ useEffect ( ( ) => {
81
+ const handleClick = e => {
82
+ const canvas = canvasRef . current ;
83
+ const hitCanvas = hitCanvasRef . current ;
84
+ const hitCtx = hitCanvas . getContext ( '2d' ) ;
85
+
86
+ // get the click coordinates
87
+ const boundingBox = canvas . getBoundingClientRect ( ) ;
88
+ const xPos = e . clientX - boundingBox . left ;
89
+ const yPos = e . clientY - boundingBox . top ;
90
+
91
+ // make sure that this click happened inside the canvas
92
+ if (
93
+ xPos > 0 &&
94
+ xPos < boundingBox . width &&
95
+ yPos > 0 &&
96
+ yPos > boundingBox . height
97
+ ) {
98
+ // get the color of the pixel clicked ony
99
+ const hitColor = hitCtx . getImageData ( xPos , yPos , 1 , 1 ) . data ;
100
+ // convert the color to a tag
101
+ const tag = colorToTag ( hitColor [ 0 ] , hitColor [ 1 ] , hitColor [ 2 ] ) ;
102
+ // call the click function with that tag. We offset tags to colors by 1 so that t-1 means there is no tag.
103
+ if ( tag !== 't-1' ) {
104
+ handleDoodleClick ( tag ) ;
105
+ }
106
+ }
107
+ } ;
108
+
109
+ document . addEventListener ( 'click' , function ( e ) {
110
+ handleClick ( e ) ;
111
+ } ) ;
112
+ } , [ handleDoodleClick ] ) ;
113
+
42
114
useEffect ( ( ) => {
43
115
const canvas = canvasRef . current ;
44
116
const ctx = canvas . getContext ( '2d' ) ;
45
- ctx . clearRect ( 0 , 0 , canvas . width , canvas . height ) ;
117
+ const hitCanvas = hitCanvasRef . current ;
118
+ const hitctx = hitCanvas . getContext ( '2d' ) ;
46
119
47
- // Draw all of the lines (reverse order so that erasing works)
48
- for ( let i = lines . length - 1 ; i >= 0 ; i -- ) {
49
- drawLine ( ctx , lines [ i ] ) ;
120
+ ctx . clearRect ( 0 , 0 , canvas . width , canvas . height ) ;
121
+ // Draw all of the doodles
122
+ for ( let d = 0 ; d < doodles . length ; d ++ ) {
123
+ const doodle = doodles [ d ] ;
124
+ let colorHash = tagToColor ( doodle . $tag ) ;
125
+ // Draw all of the lines (reverse order so that erasing works)
126
+ for ( let i = doodle . lines . length - 1 ; i >= 0 ; i -- ) {
127
+ drawLine ( ctx , hitctx , doodle . lines [ i ] , colorHash ) ;
128
+ }
50
129
}
51
- } , [ lines ] ) ;
130
+ } , [ doodles ] ) ;
52
131
53
132
return (
54
- < canvas
55
- width = { width }
56
- height = { height }
57
- ref = { canvasRef }
58
- onMouseDown = { handleMouseDown }
59
- onMouseMove = { handleMouseMove }
60
- onMouseUp = { handleMouseUp }
61
- onMouseLeave = { handleMouseLeave }
62
- />
133
+ < Fragment >
134
+ < canvas
135
+ width = { width }
136
+ height = { height }
137
+ ref = { canvasRef }
138
+ onMouseDown = { handleMouseDown }
139
+ onMouseMove = { handleMouseMove }
140
+ onMouseUp = { handleMouseUp }
141
+ onMouseLeave = { handleMouseLeave }
142
+ />
143
+ < canvas
144
+ width = { width }
145
+ height = { height }
146
+ ref = { hitCanvasRef }
147
+ style = { { visibility : 'hidden' } }
148
+ />
149
+ </ Fragment >
63
150
) ;
64
151
} ;
65
152
@@ -70,7 +157,8 @@ Canvas.propTypes = {
70
157
handleMouseUp : propTypes . func . isRequired ,
71
158
handleMouseMove : propTypes . func . isRequired ,
72
159
handleMouseLeave : propTypes . func . isRequired ,
73
- lines : propTypes . array . isRequired ,
160
+ handleDoodleClick : propTypes . func . isRequired ,
161
+ doodles : propTypes . array . isRequired ,
74
162
} ;
75
163
76
164
export { Canvas } ;
0 commit comments