diff --git a/public/js/app-paint.js b/public/js/app-paint.js new file mode 100644 index 0000000..0f7a0c5 --- /dev/null +++ b/public/js/app-paint.js @@ -0,0 +1,222 @@ +/* ============================================================ + AURA OS — Uygulama C: Neon Paint + HTML5 Canvas serbest çizim, 4 neon renk, temizle + ============================================================ */ + +const PaintAppState = {}; + +function paintBuildContent() { + const tpl = document.getElementById('tpl-paint'); + if (tpl && tpl.content) { + return document.importNode(tpl.content, true).firstElementChild.innerHTML; + } + return [ + '
', + '
', + '
', + ' ', + ' ', + ' ', + ' ', + '
', + ' ', + '
', + '
', + '
' + ].join(''); +} + +/* Canvas çizim motoru */ +function initPaintCanvas(bodyEl, canvas) { + const ctx = canvas.getContext('2d'); + + // Neon efekt için ikinci "glow" offscreen canvas + const glowCanvas = document.createElement('canvas'); + glowCanvas.width = canvas.width; + glowCanvas.height = canvas.height; + const glowCtx = glowCanvas.getContext('2d'); + glowCanvas.style.display = 'none'; + bodyEl.appendChild(glowCanvas); + + // Durum + const state = { + drawing: false, + color: '#00ffcc', + lastX: 0, + lastY: 0, + lineWidth: 4, + erasing: false, + }; + + // Canvas içindeki noktayı hesapla (konumlandırma) + function getPos(e) { + const r = canvas.getBoundingClientRect(); + const cx = canvas.width / r.width; // ölçekleme + const cy = canvas.height / r.height; + let clientX, clientY; + if (e.touches && e.touches.length) { + clientX = e.touches[0].clientX; + clientY = e.touches[0].clientY; + } else { + clientX = e.clientX; + clientY = e.clientY; + } + return { + x: (clientX - r.left) * cx, + y: (clientY - r.top) * cy, + }; + } + + /* Neon ışık saçılımı: çizgiyi birden çok katman çiziyoruz */ + function neonStroke(x0, y0, x1, y1, color, width, erase) { + if (erase) { + // Silgi modu: ikisini de sil (normal ctx) + ctx.save(); + ctx.globalCompositeOperation = 'destination-out'; + ctx.strokeStyle = 'rgba(0,0,0,1)'; + ctx.lineWidth = width; + ctx.lineCap = 'round'; + ctx.lineJoin = 'round'; + ctx.beginPath(); + ctx.moveTo(x0, y0); + ctx.lineTo(x1, y1); + ctx.stroke(); + ctx.restore(); + return; + } + + // 1) Glow offscreen katmanı (geniş + düşük opaklık, blur ile bakılır) + glowCtx.save(); + glowCtx.globalCompositeOperation = 'lighter'; + glowCtx.strokeStyle = color; + glowCtx.shadowColor = color; + glowCtx.shadowBlur = 18; + glowCtx.lineWidth = width * 2.2; + glowCtx.lineCap = 'round'; + glowCtx.lineJoin = 'round'; + glowCtx.beginPath(); + glowCtx.moveTo(x0, y0); + glowCtx.lineTo(x1, y1); + glowCtx.stroke(); + glowCtx.restore(); + + // 2) Ana çizgiyi koyu neon tonda + glowCtx.save(); + glowCtx.strokeStyle = color; + glowCtx.shadowBlur = 6; + glowCtx.lineWidth = width; + glowCtx.lineCap = 'round'; + glowCtx.beginPath(); + glowCtx.moveTo(x0, y0); + glowCtx.lineTo(x1, y1); + glowCtx.stroke(); + glowCtx.restore(); + + // 3) Parlak merkez + glowCtx.save(); + glowCtx.globalCompositeOperation = 'lighter'; + glowCtx.strokeStyle = 'rgba(255,255,255,0.7)'; + glowCtx.lineWidth = Math.max(1, width * 0.35); + glowCtx.lineCap = 'round'; + glowCtx.beginPath(); + glowCtx.moveTo(x0, y0); + glowCtx.lineTo(x1, y1); + glowCtx.stroke(); + glowCtx.restore(); + + // 4) Glow canvas'ı ana canvas'a aktar + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.drawImage(glowCanvas, 0, 0); + } + + /* ---- Olaylar ---- */ + function pointerDown(e) { + e.preventDefault(); + const p = getPos(e); + state.drawing = true; + state.lastX = p.x; + state.lastY = p.y; + // nokta + neonStroke(p.x, p.y, p.x + 0.2, p.y + 0.2, state.color, state.lineWidth, state.erasing); + } + + function pointerMove(e) { + if (!state.drawing) return; + e.preventDefault(); + const p = getPos(e); + neonStroke(state.lastX, state.lastY, p.x, p.y, state.color, state.lineWidth, state.erasing); + state.lastX = p.x; + state.lastY = p.y; + } + + function pointerEnd(e) { + state.drawing = false; + } + + // Fare + canvas.addEventListener('mousedown', pointerDown); + window.addEventListener('mousemove', pointerMove); + window.addEventListener('mouseup', pointerEnd); + + // Dokunmatik + canvas.addEventListener('touchstart', pointerDown, { passive: false }); + canvas.addEventListener('touchmove', pointerMove, { passive: false }); + canvas.addEventListener('touchend', pointerEnd); + + // Sağ lob fare: silgi modu (contextmenu bastır) + canvas.addEventListener('contextmenu', (e) => e.preventDefault()); + + // Renk seçim butonları + const colorBtns = bodyEl.querySelectorAll('.color-btn'); + colorBtns.forEach(btn => { + btn.addEventListener('click', () => { + state.color = btn.dataset.color; + state.erasing = false; + colorBtns.forEach(b => b.classList.remove('active')); + btn.classList.add('active'); + // canvas imleci rengi + canvas.style.cursor = 'crosshair'; + }); + }); + + // Temizle butonu + const clearBtn = bodyEl.querySelector('.paint-clear-btn'); + if (clearBtn) { + clearBtn.addEventListener('click', () => { + ctx.clearRect(0, 0, canvas.width, canvas.height); + glowCtx.clearRect(0, 0, glowCanvas.width, glowCanvas.height); + }); + } + + // Initial draw: boş arka planı + ctx.fillStyle = '#05060f'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + glowCtx.fillStyle = '#05060f'; + glowCtx.fillRect(0, 0, canvas.width, canvas.height); + + // Shift silgiyi keskinleştir — imleç css + state.color = '#00ffcc'; + canvas.style.cursor = 'crosshair'; + + return state; +} + +registerApp('paint', { + name: 'Neon Paint', + icon: '🎨', + defaultWidth: 780, + defaultHeight: 520, + windowTitle: 'Neon Paint — Aura OS', + windowProps: { width: 780, height: 520 }, + + buildContent: paintBuildContent, + + onLaunch(record, bodyEl) { + bodyEl.innerHTML = paintBuildContent(); + + const canvas = bodyEl.querySelector('.paint-canvas'); + if (!canvas) return; + + initPaintCanvas(bodyEl, canvas); + }, +});