const canvas = document.getElementById('canvas') const ctx = canvas.getContext('2d') const width = canvas.width; const height = canvas.height; let current let selection = [] let xxx let yyy let selectWidth let selectHeight let cyka let blyat const tools = { graffity: { mousemove(e){ //e.buttons 0b00000x11 & 0b00000100 == x (e.buttons & 1) && new Circle(e.clientX, e.clientY, +size.value, color.value) } }, circle: { mousedown(e){ current = new Circle(e.clientX,e.clientY, 1, color.value) }, mousemove(e){ if (!current) return; current.radius = current.distanceTo(e.clientX, e.clientY) Drawable.drawAll() }, mouseup(e){ current = null } }, ellipse: { mousedown(e) { current = new Ellipse(e.clientX, e.clientY, 5, 5, Math.PI / 2, 0, 2 * Math.PI, color.value); }, mousemove(e) { if (!current) return; current.radiusX = Math.abs(e.clientY - current.y) current.radiusY = Math.abs(e.clientX - current.x) Drawable.drawAll(); }, mouseup(e) { current = null; }, }, line: { mousedown(e){ current = new Line(e.clientX, e.clientY, 0, 0, color.value, +size.value) }, mousemove(e){ if (!current) return; current.width = e.clientX - current.x current.height = e.clientY - current.y Drawable.drawAll() }, mouseup(e){ current = null } }, rect: { mousedown(e) { current = new Rectangle(e.clientX, e.clientY, 0, 0, color.value); }, mousemove(e) { if (!current) return; current.width = e.clientX - current.x; current.height = e.clientY - current.y; Drawable.drawAll(); }, mouseup(e) { if (current.width < 0) { current.x = current.x + current.width current.width = Math.abs(current.width) } if (current.height < 0) { current.y = current.y + current.height current.height = Math.abs(current.height) } current = null; }, }, select: { click(e) { if (!blyat) { let found = Drawable.instances.filter(c => c.in && c.in(e.clientX, e.clientY)) if (found.length) { if (e.ctrlKey) { selection.push(found.pop()) } else { selection = [found.pop()] } } else { if (!e.ctrlKey) selection = [] } Drawable.drawAll(selection) } cyka = false blyat = false }, mousedown(e) { xxx = e.clientX, yyy = e.clientY cyka = true current = new RectangleForSelect(xxx, yyy, selectWidth, selectHeight) }, mousemove(e) { if (cyka && current) { blyat = true current.width = e.clientX - current.x; current.height = e.clientY - current.y; selectWidth = current.width selectHeight = current.height Drawable.drawAll(selection) } }, mouseup(e) { if (current.width < 0) { xxx += current.width selectWidth = Math.abs(current.width) } if (current.height < 0) { yyy += current.height selectHeight = Math.abs(current.height) } Drawable.instances.pop() current = null let found = Drawable.instances.filter(c => c.inBounds && c.inBounds(xxx, yyy, selectWidth, selectHeight)) if (found.length) { selection.push(...found) } else { selection = [...selection] } Drawable.drawAll(selection) }, } } 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; i= x && this.x <= x + w && this.y >= y && this.y <= y + h } } class Line extends Drawable { 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.lineTo(this.x + this.width, this.y + this.height); ctx.closePath(); if (selected){ ctx.strokeStyle = 'yellow' ctx.stroke(); } else { ctx.strokeStyle = this.color; ctx.lineWidth = this.lineWidth ctx.stroke(); } } in(x, y) { let angle1 = Math.atan2(this.height, this.width) let angle2 = Math.atan2(y - this.y, x - this.x) let angle3 = angle2 - angle1 let line = distance(this.x, this.y, x, y) let redWidth = Math.cos(angle3) * line let redHeight = Math.sin(angle3) * line return (redWidth < distance(this.x, this.y, this.x + this.width, this.y + this.height)) && (Math.abs(redHeight) < (this.lineWidth / 2)) } } class Rectangle extends Drawable { constructor(x, y, width, height, color) { super() this.x = x; this.y = y; this.width = width; this.height = height; this.color = color;; } draw(selected) { ctx.beginPath(); ctx.rect(this.x, this.y, this.width, this.height); ctx.closePath(); ctx.fillStyle = this.color; if (selected) { ctx.lineWidth = 5 ctx.strokeStyle = 'yellow' ctx.stroke(); } ctx.fill(); } in(x,y){ return x > this.x && y > this.y && x < this.x + this.width && y < this.y + this.height } inBounds(x,y,w,h){ // x = 100, this.x = 102, w = 5 return this.x >= x && this.x + this.width <= x + w && this.y >= y && this.y + this.height <= y + h } } class RectangleForSelect extends Rectangle { constructor(x, y, width, height) { super(x, y, width, height) } draw() { ctx.beginPath(); ctx.rect(this.x, this.y, this.width, this.height); ctx.closePath(); ctx.strokeStyle = 'black' ctx.lineWidth = '2' ctx.stroke(); } } class Ellipse extends Drawable { constructor(x, y, radiusX, radiusY, rotation, startAngle, endAngle, color){ super() this.x = x; this.y = y; this.radiusX = radiusX this.radiusY = radiusY this.rotation = rotation; this.startAngle = startAngle this.endAngle = endAngle this.color = color; this.draw(); } draw(selected){ ctx.beginPath(); ctx.ellipse(this.x, this.y, this.radiusX, this.radiusY, this.rotation, this.startAngle, this.endAngle); ctx.closePath(); ctx.fillStyle = this.color; if (selected){ ctx.lineWidth = 5 ctx.strokeStyle = 'yellow' ctx.stroke(); } ctx.fill(); } in(x,y){ return (Math.abs(y - this.y) < this.radiusX && Math.abs(x - this.x) < this.radiusY) } inBounds(x,y,w,h){ // x = 100, this.x = 102, w = 5 return this.x >= x && this.x <= x + w && this.y >= y && this.y <= y + h } } 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() } undo.onclick = function(){ Drawable.instances.pop() Drawable.drawAll() }