const canvas = document.getElementById('canvas') const ctx = canvas.getContext('2d') const width = canvas.width; const height = canvas.height; let current; let selection = [] const tools = { graffity: { mousemove(e){ //e.buttons 0b00000x11 & 0b00000100 == x (e.buttons & 0b001) && new Circle(e.layerX, e.layerY, +size.value, color.value, +size.value) } }, circle: { mousedown(e){ current = new Circle(e.layerX,e.layerY, 1, color.value) }, mousemove(e){ if (!current) return; current.radius = current.distanceTo(e.layerX, e.layerY) Drawable.drawAll() }, mouseup(e){ current = null } }, ellipse: { mousedown(e){ current = new Ellipse(e.layerX, e.layerY, 0, 0, color.value, +size.value) }, mousemove(e){ if (!current) return; current.width = e.layerX - current.x current.height = e.layerY - current.y Drawable.drawAll() }, mouseup(e){ current = null } }, line: { mousedown(e){ current = new Line(e.layerX, e.layerY, 0, 0, color.value, +size.value) }, mousemove(e){ if (!current) return; current.width = e.layerX - current.x current.height = e.layerY - current.y Drawable.drawAll() }, mouseup(e){ current = null } }, rectangle: { mousedown(e){ current = new Rectangle(e.layerX, e.layerY, 0, 0, color.value, +size.value) }, mousemove(e){ if (!current) return; current.width = e.layerX - current.x current.height = e.layerY - current.y Drawable.drawAll() }, mouseup(e){ current = null } }, select: { click(e) { selection = []; e.stopImmediatePropagation(); if (current.width||current.height){ let foundInBounds = Drawable.instances.filter(c => c.inBounds && c.inBounds(current.x, current.y, current.width, current.height)) console.log('foundInBounds:'); console.log(foundInBounds); if (foundInBounds.length) { selection = [...foundInBounds]; } Drawable.instances.pop(); } else { if (!selection.length) { let found = Drawable.instances.filter(c => c.in && c.in(e.layerX, e.layerY)) console.log('found:') console.log(found) if (found.length) { if (e.ctrlKey) { selection.push(found.pop()) } else { selection = [found.pop()] } } else { if (!e.ctrlKey) selection = [] } } } Drawable.drawAll(selection) current = null; }, mousedown(e){ // current = new BoundsRectangle(e.layerX, e.layerY, 0, 0, 'grey', +size.value) }, mousemove(e) { if (!current) return; current.width = e.layerX - current.x current.height = e.layerY - current.y Drawable.drawAll() }, mouseup(e){ //x,y, w, h прямоугольника //selection - только те элеменеты Drawable.instances которые в границах прямоугольника. { console.log(e); } }, } } function superHandler(evt){ let t = tools[tool.value] if (typeof t[evt.type] === 'function') t[evt.type].call(this, evt) } canvas.onmousemove = superHandler canvas.onmouseup = superHandler canvas.onmousedown = superHandler canvas.onclick = superHandler //// function Drawable(){ Drawable.addInstance(this); } const distance = (x1,y1, x2, y2) => ((x1-x2)**2 + (y1-y2)**2)**0.5 Drawable.prototype.draw = function(){}; Drawable.prototype.distanceTo = function(x,y){ if (typeof this.x !== 'number' || typeof this.y !== 'number'){ return NaN } return distance(this.x, this.y, x, y) }; Drawable.instances = []; Drawable.addInstance = function(item){ Drawable.instances.push(item); } Drawable.drawAll = function(selection=[]){ ctx.clearRect(0,0,width,height); Drawable.forAll(item => item.draw()) selection.forEach(item => item.draw(true)) } Drawable.forAll = function(callback){ for(var i = 0; i0)?(this.x-this.radius >= x && this.x+this.radius <= x + w):(this.x-this.radius >= x+w && this.x+this.radius <= x)) && ((h>0)?(this.y-this.radius >= y && this.y+this.radius <= y + h ):(this.y-this.radius >= y+h && this.y+this.radius <= y )) } } class Ellipse extends Drawable { //__proto__: Drawable.prototype constructor(x,y, width, height, color, lineWidth){ super() this.x = x; this.y = y; this.width = width; this.height = height; this.color = color; this.lineWidth = lineWidth; this.draw(); } draw(selected){ ctx.beginPath(); ctx.moveTo(this.x, this.y); ctx.ellipse(this.x, this.y, ((this.width**2)**0.5), ((this.height**2)**0.5), 0, 0, Math.PI * 2) ctx.closePath(); ctx.strokeStyle = this.color; ctx.fillStyle = this.color; ctx.lineWidth = this.lineWidth; if (selected){ ctx.lineWidth = 2 ctx.stroke(); } ctx.fill(); } in (x, y){ return ( ((((x - this.x) ** 2) / ((this.width ) ** 2)) + (((y - this.y) ** 2 )/( (this.height) ** 2))) <= 1 ) } inBounds(x, y, w, h) { return ((w>0)?(this.x-this.width >= x && this.x+this.width <= x + w):(this.x-this.width >= x+w && this.x+this.width <= x)) && ((h>0)?(this.y-this.height >= y && this.y+this.height <= y + h ):(this.y-this.height >= y+h && this.y+this.height <= y )) } } class Line extends Drawable { //смочь скопировать в Rectangle и поменять отприсовку constructor(x,y, width, height, color, lineWidth){ super() this.x = x; this.y = y; this.width = width; this.height = height; this.color = color; this.lineWidth = lineWidth; this.draw(); } draw(){ ctx.beginPath(); ctx.moveTo(this.x, this.y); ctx.lineTo(this.x + this.width, this.y + this.height); ctx.closePath(); ctx.strokeStyle = this.color; ctx.lineWidth = this.lineWidth ctx.stroke(); } in(x, y) { const deltaAngle = Math.atan2(y - this.y, x - this.x) - Math.atan2(this.height, this.width) ; const newX = Math.cos(deltaAngle) * this.distanceTo(x, y); const newY = Math.sin(deltaAngle) * this.distanceTo(x, y); return ((newX>=0)&&(newX<=distance(0,0, this.width, this.height))&&(newY>=-this.lineWidth/2)&&(newY<=this.lineWidth/2) ) } inBounds(x, y, w, h) { let areaX = false; let areaY = false; const angleX = Math.atan2(this.height, this.width); const deltaX = Math.sin(angleX) * this.lineWidth/2; const deltaY = Math.cos(angleX) * this.lineWidth/2; if (this.width > 0) { areaX=(w > 0) ? (this.x-deltaX >= x && this.x + this.width+deltaX <= x + w):(this.x-deltaX >= x + w && this.x + this.width+deltaX <= x)} else { areaX= ((w > 0) ? (this.x+ this.width-deltaX >= x && this.x+deltaX <= x + w):(this.x + this.width-deltaX>= x + w && this.x+deltaX <= x)) } if (this.height > 0) { areaY=(h > 0) ? (this.y-deltaY >= y && this.y + this.height+deltaY <= y + h):(this.y-deltaY >= y + h && this.y + this.height+deltaY <= y)} else { areaY= (h > 0) ? (this.y+ this.height-deltaY >= y && this.y+deltaY <= y + h):(this.y + this.height-deltaY>= y + h && this.y+deltaY<= y) } return areaX && areaY; } } color.onchange = () => { selection.forEach(c => c.color = color.value) Drawable.drawAll(selection) } document.getElementById('delete').onclick = () =>{ Drawable.instances = Drawable.instances.filter(item => !selection.includes(item)) selection = [] Drawable.drawAll() } class Rectangle extends Drawable { //смочь скопировать в Rectangle и поменять отприсовку constructor(x, y, width, height, color, lineWidth) { super() this.x = x; this.y = y; this.width = width; this.height = height; this.color = color; this.lineWidth = lineWidth; this.draw(); } draw(selected){ ctx.beginPath(); ctx.moveTo(this.x, this.y); ctx.rect(this.x, this.y, this.width, this.height) ctx.closePath(); ctx.strokeStyle = this.color; ctx.fillStyle = this.color; ctx.lineWidth = this.lineWidth; if (selected){ ctx.lineWidth = 5 ctx.stroke(); } ctx.fill(); } in(x, y) { return ( ((this.width > 0) ? (this.x <= x && x <= (this.x + this.width)) : (this.x + this.width <= x && x <= this.x)) && ( (this.height>0)? (this.y<= y && y <= (this.y + this.height)):( this.y+this.height<= y && y <= this.y)) ) } inBounds(x, y, w, h) { let areaX = false; let areaY = false; if (this.width > 0) { areaX=(w > 0) ? (this.x >= x && this.x+ this.width <= x + w):(this.x >= x + w && this.x + this.width <= x)} else { areaX= ((w > 0) ? (this.x+ this.width >= x && this.x <= x + w):(this.x + this.width>= x + w && this.x <= x)) } if (this.height > 0) { areaY=(h > 0) ? (this.y >= y && this.y + this.height <= y + h):(this.y>= y + h && this.y + this.height <= y)} else { areaY= (h > 0) ? (this.y+ this.height >= y && this.y <= y + h):(this.y + this.height>= y + h && this.y <= y) } return areaX && areaY; } } class BoundsRectangle extends Drawable { //смочь скопировать в Rectangle и поменять отприсовку constructor(x, y, width, height, color, lineWidth) { super() this.x = x; this.y = y; this.width = width; this.height = height; this.color = 'grey'; this.lineWidth = 2; this.draw(); } draw(){ ctx.beginPath(); ctx.moveTo(this.x, this.y); ctx.rect(this.x, this.y, this.width, this.height) ctx.closePath(); ctx.strokeStyle = this.color; ctx.lineWidth = this.lineWidth; ctx.stroke(); } } color.onchange = () => { selection.forEach(c => c.color = color.value) Drawable.drawAll(selection) } document.getElementById('delete').onclick = () =>{ Drawable.instances = Drawable.instances.filter(item => !selection.includes(item)) selection = [] Drawable.drawAll() } //new Line(0,0,100,100, "red") ////new Circle(30,30,10, "red") ////canvas.onmousemove = function(e){ ////} //undo.onclick = function(){ //Drawable.instances.pop() ////Drawable.instances = [] //Drawable.drawAll() //}