@@ -14,6 +14,8 @@ import iterateGrid from './utils/interateGrid';
1414import bitmaskToPath from './utils/bitmapToMask' ;
1515import createLayer from './utils/createLayer' ;
1616
17+ type Pixel = { x : number , y : number } ;
18+
1719@Component ( {
1820 selector : 'pg-input-pixel-editor' ,
1921 style,
@@ -58,10 +60,42 @@ export default class PgInputPixelEditor extends HTMLElement {
5860 if ( context === null ) { return ; }
5961 this . #context = context ;
6062 // Wire Up Events
63+ this . $canvas . addEventListener (
64+ 'contextMenu' ,
65+ this . handleContextMenu . bind ( this )
66+ ) ;
67+ this . $canvas . addEventListener (
68+ 'doubleClick' ,
69+ this . handleDoubleClick . bind ( this )
70+ ) ;
6171 this . $canvas . addEventListener (
6272 'pointerdown' ,
6373 this . handlePointerDown . bind ( this )
6474 ) ;
75+ this . $canvas . addEventListener (
76+ 'pointerup' ,
77+ this . handlePointerUp . bind ( this )
78+ ) ;
79+ this . $canvas . addEventListener (
80+ 'pointermove' ,
81+ this . handlePointerMove . bind ( this )
82+ ) ;
83+ this . $canvas . addEventListener (
84+ 'pointerenter' ,
85+ this . handlePointerEnter . bind ( this )
86+ ) ;
87+ this . $canvas . addEventListener (
88+ 'pointerleave' ,
89+ this . handlePointerLeave . bind ( this )
90+ ) ;
91+ this . $canvas . addEventListener (
92+ 'keydown' ,
93+ this . handleKeyDown . bind ( this )
94+ ) ;
95+ this . $canvas . addEventListener (
96+ 'keyup' ,
97+ this . handleKeyUp . bind ( this )
98+ ) ;
6599 }
66100
67101 render ( changes ) {
@@ -76,6 +110,7 @@ export default class PgInputPixelEditor extends HTMLElement {
76110 const actualHeight = this . height * totalSize - this . gridSize ;
77111 this . $canvas . width = actualWidth ;
78112 this . $canvas . height = actualHeight ;
113+ this . #data = fillGrid ( this . width , this . height ) ;
79114 [ this . #baseLayer, this . #baseLayerContext] = createLayer ( actualWidth , actualHeight ) ;
80115 [ this . #editLayer, this . #editLayerContext] = createLayer ( actualWidth , actualHeight ) ;
81116 [ this . #noEditLayer, this . #noEditLayerContext] = createLayer ( actualWidth , actualHeight ) ;
@@ -96,7 +131,7 @@ export default class PgInputPixelEditor extends HTMLElement {
96131 #delayTimerId: number = 0 ;
97132 #delayedChange( ) {
98133 clearInterval ( this . #delayTimerId) ;
99- this . #delayTimerId = window . setTimeout ( this . #handleChange, 1000 ) ;
134+ this . #delayTimerId = window . setTimeout ( this . #handleChange. bind ( this ) , 1000 ) ;
100135 } ;
101136
102137 #setPixel ( x : number , y : number , color : number ) {
@@ -144,7 +179,72 @@ export default class PgInputPixelEditor extends HTMLElement {
144179 // Verify this is the only place setting pixel data!
145180 this . #data[ y ] [ x ] = color ;
146181 this . #delayedChange( ) ;
147- } ;
182+ }
183+
184+ #setPreview( pixels : Pixel [ ] , previousX : number , previousY : number ) {
185+ const totalSize = this . size + this . gridSize ;
186+ const actualWidth = this . width * totalSize - this . gridSize ;
187+ const actualHeight = this . height * totalSize - this . gridSize ;
188+ const { minX, maxX, minY, maxY } = pixels . reduce ( ( previous , current ) => {
189+ return {
190+ minX : Math . min ( previous . minX , current . x , previousX ) ,
191+ maxX : Math . max ( previous . maxX , current . x , previousX ) ,
192+ minY : Math . min ( previous . minY , current . y , previousY ) ,
193+ maxY : Math . max ( previous . maxY , current . y , previousY )
194+ } ;
195+ } , { minX : this . width , maxX : 0 , minY : this . height , maxY : 0 } ) ;
196+ // base layer to main canvas
197+ this . #context. drawImage (
198+ this . #baseLayer,
199+ minX * totalSize , minY * totalSize , maxX * totalSize , maxY * totalSize ,
200+ minX * totalSize , minY * totalSize , maxX * totalSize , maxY * totalSize
201+ ) ;
202+ // edit to main canvas
203+ this . #context. drawImage (
204+ this . #editLayer,
205+ minX * totalSize , minY * totalSize , maxX * totalSize , maxY * totalSize ,
206+ minX * totalSize , minY * totalSize , maxX * totalSize , maxY * totalSize
207+ ) ;
208+ // preview layer
209+ this . #previewLayerContext. clearRect ( 0 , 0 , actualWidth , actualHeight ) ;
210+ pixels . forEach ( ( { x, y } ) => {
211+ this . #previewLayerContext. fillStyle = WHITE ;
212+ this . #previewLayerContext. beginPath ( ) ;
213+ this . #previewLayerContext. arc ( x * totalSize + 5 , y * totalSize + 5 , 3 , 0 , 2 * Math . PI ) ;
214+ this . #previewLayerContext. closePath ( ) ;
215+ this . #previewLayerContext. fill ( ) ;
216+ this . #previewLayerContext. fillStyle = '#1B79C8' ;
217+ this . #previewLayerContext. beginPath ( ) ;
218+ this . #previewLayerContext. arc ( x * totalSize + 5 , y * totalSize + 5 , 2 , 0 , 2 * Math . PI ) ;
219+ this . #previewLayerContext. closePath ( ) ;
220+ this . #previewLayerContext. fill ( ) ;
221+ } ) ;
222+ // preview layer to main canvas
223+ this . #context. drawImage (
224+ this . #previewLayer,
225+ minX * totalSize , minY * totalSize , maxX * totalSize , maxY * totalSize ,
226+ minX * totalSize , minY * totalSize , maxX * totalSize , maxY * totalSize
227+ ) ;
228+ console . log ( 'render preview' , minX , minY , maxX , maxY ) ;
229+ }
230+
231+ handleKeyDown ( event : KeyboardEvent ) {
232+ if ( event . key === ' ' ) {
233+ console . log ( 'space!' )
234+ }
235+ }
236+
237+ handleKeyUp ( event : KeyboardEvent ) {
238+
239+ }
240+
241+ handleContextMenu ( event : MouseEvent ) {
242+ event ?. preventDefault ( ) ;
243+ }
244+
245+ handleDoubleClick ( event : MouseEvent ) {
246+ event ?. preventDefault ( ) ;
247+ }
148248
149249 handlePointerDown ( event : MouseEvent ) {
150250 if ( event . buttons !== 1 && event . buttons !== 32 ) {
@@ -227,6 +327,97 @@ export default class PgInputPixelEditor extends HTMLElement {
227327 this . #isPressed = false ;
228328 }
229329
330+ handlePointerMove ( event : PointerEvent ) {
331+ const canvas = this . $canvas ;
332+ if ( this . #isPressed) {
333+ const data = this . #data;
334+ const rect = canvas . getBoundingClientRect ( ) ;
335+ const totalSize = this . size + this . gridSize ;
336+ const points : [ number , number ] [ ] = [ ] ;
337+ const startX = this . #startX;
338+ const startY = this . #startY;
339+ const x = this . #x;
340+ const y = this . #y;
341+ // If supported get all the inbetween points
342+ // really noticable for pen support + pencil tool
343+ if ( typeof event . getCoalescedEvents === 'function' ) {
344+ const events = event . getCoalescedEvents ( ) ;
345+ for ( const evt of events ) {
346+ let tX = Math . floor ( ( evt . clientX - rect . left ) / totalSize ) ;
347+ let tY = Math . floor ( ( evt . clientY - rect . top ) / totalSize ) ;
348+ if ( tX >= this . width || tY >= this . height || ( tX === x && tY === y ) ) {
349+ continue ;
350+ }
351+ points . push ( [ tX , tY ] ) ;
352+ }
353+ } else {
354+ let newX = Math . floor ( ( event . clientX - rect . left ) / totalSize ) ;
355+ let newY = Math . floor ( ( event . clientY - rect . top ) / totalSize ) ;
356+ if ( newX === x && newY === y ) { return ; }
357+ if ( newX >= this . width ) { newX = this . width - 1 ; }
358+ if ( newY >= this . height ) { newY = this . height - 1 ; }
359+ points . push ( [ newX , newY ] ) ;
360+ }
361+ // Is Eraser
362+ const color = event . buttons === 32 ? 0 : 1 ;
363+ // Shape tools only care about the last point
364+ if ( points . length === 0 ) { return ; }
365+ const [ lastX , lastY ] = points . at ( - 1 ) as [ number , number ] ;
366+ // This is not ideal, but might be good enough,
367+ // really it should be finding the point furthest absolute
368+ // point from startX/startY.
369+ this . #x = lastX ;
370+ this . #y = lastY ;
371+ switch ( this . #inputMode) {
372+ case InputMode . Pixel :
373+ for ( var point of points ) {
374+ this . #setPixel( point [ 0 ] , point [ 1 ] , color ) ;
375+ data [ point [ 1 ] ] [ point [ 0 ] ] = color ;
376+ }
377+ break ;
378+ case InputMode . Line :
379+ console . log ( x , y )
380+ this . #setPreview( getLinePixels ( startX , startY , lastX , lastY ) , x , y ) ;
381+ break ;
382+ case InputMode . Rectangle :
383+ this . #setPreview( getRectanglePixels ( startX , startY , lastX , lastY ) , x , y ) ;
384+ break ;
385+ case InputMode . RectangleOutline :
386+ this . #setPreview( getRectangleOutlinePixels ( startX , startY , lastX , lastY ) , x , y ) ;
387+ break ;
388+ case InputMode . Ellipse :
389+ this . #setPreview( getEllipseOutlinePixels ( startX , startY , lastX , lastY ) , x , y ) ;
390+ break ;
391+ case InputMode . EllipseOutline :
392+ this . #setPreview( getEllipseOutlinePixels ( startX , startY , lastX , lastY ) , x , y ) ;
393+ break ;
394+ }
395+ }
396+ }
397+
398+ handlePointerEnter ( event : MouseEvent ) {
399+ console . log ( 'hmmmm' )
400+ if ( ! this . #isPressed && ! this . #isEditing) {
401+ this . #isEditing = true ;
402+ // base layer to main canvas
403+ this . #context. drawImage ( this . #baseLayer, 0 , 0 ) ;
404+ // editing layer to main canvas
405+ this . #context. drawImage ( this . #isEditing ? this . #editLayer : this . #noEditLayer, 0 , 0 ) ;
406+ }
407+ console . log ( 'enter' ) ;
408+ }
409+
410+ handlePointerLeave ( event : MouseEvent ) {
411+ if ( ! this . #isPressed) {
412+ this . #isEditing = false ;
413+ // base layer to main canvas
414+ this . #context. drawImage ( this . #baseLayer, 0 , 0 ) ;
415+ // editing layer to main canvas
416+ this . #context. drawImage ( this . #isEditing ? this . #editLayer : this . #noEditLayer, 0 , 0 ) ;
417+ }
418+ console . log ( 'leave' ) ;
419+ }
420+
230421 #updateGrid( ) {
231422 // base layer to main canvas
232423 this . #context. drawImage ( this . #baseLayer, 0 , 0 ) ;
0 commit comments