Skip to content

Commit ed10703

Browse files
committed
add tileable painting
1 parent 894ea41 commit ed10703

1 file changed

Lines changed: 75 additions & 24 deletions

File tree

index.html

Lines changed: 75 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -819,6 +819,13 @@
819819
</div>
820820
<span class="value-display" id="manualDirectionValue"></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

Comments
 (0)