819819 </ div >
820820 < span class ="value-display " id ="manualDirectionValue "> 0°</ span >
821821 </ div >
822+ </ div >
823+
824+ < div class ="property-group ">
825+ < div class ="checkbox-wrapper ">
826+ < input type ="checkbox " id ="tileablePainting ">
827+ < label for ="tileablePainting " class ="property-label " style ="margin-bottom: 0; "> Tileable Painting</ label >
828+ </ div >
822829 </ div >
823830 </ div >
824831 </ div >
@@ -970,6 +977,7 @@ <h2 class="modal-title">Export Flow Map</h2>
970977 this . mouseDirection = 0 ;
971978 this . useMouseDirection = true ;
972979 this . manualDirection = 0 ;
980+ this . isTileable = false ;
973981
974982 this . lastX = 0 ;
975983 this . lastY = 0 ;
@@ -1088,11 +1096,14 @@ <h2 class="modal-title">Export Flow Map</h2>
10881096 setupEventListeners ( ) {
10891097 // Canvas events
10901098 this . canvas . addEventListener ( 'mousedown' , this . startDrawing . bind ( this ) ) ;
1091- this . canvas . addEventListener ( 'mousemove' , this . handleMouseMove . bind ( this ) ) ;
1092- this . canvas . addEventListener ( 'mouseup' , this . stopDrawing . bind ( this ) ) ;
1093- this . canvas . addEventListener ( 'mouseleave' , this . stopDrawing . bind ( this ) ) ;
1099+
1100+ window . addEventListener ( 'mousemove' , this . handleMouseMove . bind ( this ) ) ;
1101+ window . addEventListener ( 'mouseup' , this . stopDrawing . bind ( this ) ) ;
1102+
10941103 this . canvas . addEventListener ( 'mouseenter' , this . showBrushCursor . bind ( this ) ) ;
1095- this . canvas . addEventListener ( 'mouseleave' , this . hideBrushCursor . bind ( this ) ) ;
1104+ this . canvas . addEventListener ( 'mouseleave' , ( ) => {
1105+ if ( ! this . isDrawing ) this . hideBrushCursor ( ) ;
1106+ } ) ;
10961107 this . canvas . addEventListener ( 'wheel' , this . handleWheel . bind ( this ) , { passive : false } ) ;
10971108
10981109 // Touch events
@@ -1122,6 +1133,10 @@ <h2 class="modal-title">Export Flow Map</h2>
11221133 this . useMouseDirection = e . target . checked ;
11231134 document . getElementById ( 'manualDirectionGroup' ) . style . display = this . useMouseDirection ? 'none' : 'block' ;
11241135 } ) ;
1136+
1137+ document . getElementById ( 'tileablePainting' ) . addEventListener ( 'change' , ( e ) => {
1138+ this . isTileable = e . target . checked ;
1139+ } ) ;
11251140
11261141 // Radial direction picker
11271142 const directionPicker = document . getElementById ( 'directionPicker' ) ;
@@ -1359,6 +1374,34 @@ <h2 class="modal-title">Export Flow Map</h2>
13591374 this . updateCanvas ( ) ;
13601375 }
13611376
1377+ applyTiledOperation ( x , y , radius , callback ) {
1378+ if ( ! this . isTileable ) {
1379+ callback ( x , y , 0 , 0 ) ;
1380+ return ;
1381+ }
1382+
1383+ const w = this . canvas . width ;
1384+ const h = this . canvas . height ;
1385+
1386+ const offsetsX = [ 0 ] ;
1387+ if ( x + radius >= w ) offsetsX . push ( - w ) ;
1388+ if ( x - radius <= 0 ) offsetsX . push ( w ) ;
1389+
1390+ const offsetsY = [ 0 ] ;
1391+ if ( y + radius >= h ) offsetsY . push ( - h ) ;
1392+ if ( y - radius <= 0 ) offsetsY . push ( h ) ;
1393+
1394+ for ( const ox of offsetsX ) {
1395+ for ( const oy of offsetsY ) {
1396+ if ( ox === 0 && oy === 0 ) {
1397+ callback ( x , y , 0 , 0 ) ;
1398+ } else {
1399+ callback ( x + ox , y + oy , ox , oy ) ;
1400+ }
1401+ }
1402+ }
1403+ }
1404+
13621405 drawFlowBrush ( ctx , x , y , scale ) {
13631406 const size = this . brushSize * scale ;
13641407
@@ -1387,12 +1430,14 @@ <h2 class="modal-title">Export Flow Map</h2>
13871430 const g = Math . floor ( 128 + dirY * 127 ) ;
13881431 const b = 0 ; // Blue channel set to 0
13891432
1390- const gradient = ctx . createRadialGradient ( x , y , 0 , x , y , size ) ;
1391- gradient . addColorStop ( 0 , `rgba(${ r } , ${ g } , ${ b } , ${ this . flowStrength } )` ) ;
1392- gradient . addColorStop ( 1 , `rgba(${ r } , ${ g } , ${ b } , 0)` ) ;
1393-
1394- ctx . fillStyle = gradient ;
1395- ctx . fillRect ( x - size , y - size , size * 2 , size * 2 ) ;
1433+ this . applyTiledOperation ( x , y , size , ( drawX , drawY ) => {
1434+ const gradient = ctx . createRadialGradient ( drawX , drawY , 0 , drawX , drawY , size ) ;
1435+ gradient . addColorStop ( 0 , `rgba(${ r } , ${ g } , ${ b } , ${ this . flowStrength } )` ) ;
1436+ gradient . addColorStop ( 1 , `rgba(${ r } , ${ g } , ${ b } , 0)` ) ;
1437+
1438+ ctx . fillStyle = gradient ;
1439+ ctx . fillRect ( drawX - size , drawY - size , size * 2 , size * 2 ) ;
1440+ } ) ;
13961441 }
13971442
13981443 drawEraser ( ctx , x , y , scale ) {
@@ -1401,20 +1446,22 @@ <h2 class="modal-title">Export Flow Map</h2>
14011446 // Set composite operation to erase
14021447 ctx . globalCompositeOperation = 'destination-out' ;
14031448
1404- // Draw eraser stroke
1405- ctx . beginPath ( ) ;
1406- ctx . moveTo ( this . lastX , this . lastY ) ;
1407- ctx . lineTo ( x , y ) ;
1408- ctx . strokeStyle = 'rgba(0,0,0,1)' ;
1409- ctx . lineWidth = size * 2 ;
1410- ctx . lineCap = 'round' ;
1411- ctx . lineJoin = 'round' ;
1412- ctx . stroke ( ) ;
1413-
1414- // Also draw a circle for single clicks
1415- ctx . beginPath ( ) ;
1416- ctx . arc ( x , y , size , 0 , Math . PI * 2 ) ;
1417- ctx . fill ( ) ;
1449+ this . applyTiledOperation ( x , y , size , ( drawX , drawY , ox , oy ) => {
1450+ // Draw eraser stroke
1451+ ctx . beginPath ( ) ;
1452+ ctx . moveTo ( this . lastX + ox , this . lastY + oy ) ;
1453+ ctx . lineTo ( drawX , drawY ) ;
1454+ ctx . strokeStyle = 'rgba(0,0,0,1)' ;
1455+ ctx . lineWidth = size * 2 ;
1456+ ctx . lineCap = 'round' ;
1457+ ctx . lineJoin = 'round' ;
1458+ ctx . stroke ( ) ;
1459+
1460+ // Also draw a circle for single clicks
1461+ ctx . beginPath ( ) ;
1462+ ctx . arc ( drawX , drawY , size , 0 , Math . PI * 2 ) ;
1463+ ctx . fill ( ) ;
1464+ } ) ;
14181465
14191466 // Reset composite operation
14201467 ctx . globalCompositeOperation = 'source-over' ;
@@ -1424,6 +1471,10 @@ <h2 class="modal-title">Export Flow Map</h2>
14241471 if ( this . isDrawing ) {
14251472 this . isDrawing = false ;
14261473 this . saveHistory ( ) ;
1474+
1475+ if ( ! this . canvas . matches ( ':hover' ) ) {
1476+ this . hideBrushCursor ( ) ;
1477+ }
14271478 }
14281479 }
14291480
0 commit comments