from .labwidget import Widget, Property, minify import html class PaintWidget(Widget): def __init__(self, width=256, height=256, image='', mask='', brushsize=10.0, oneshot=False, disabled=False, vanishing=True, opacity=0.7, **kwargs): super().__init__(**kwargs) self.mask = Property(mask) self.image = Property(image) self.vanishing = Property(vanishing) self.brushsize = Property(brushsize) self.erase = Property(False) self.oneshot = Property(oneshot) self.disabled = Property(disabled) self.width = Property(width) self.height = Property(height) self.opacity = Property(opacity) self.startpos = Property(None) self.dragpos = Property(None) self.dragging = Property(False) def widget_js(self): return minify(f''' {PAINT_WIDGET_JS} var pw = new PaintWidget(element, model); ''') def widget_html(self): v = self.view_id() return minify(f'''
''') PAINT_WIDGET_JS = """ class PaintWidget { constructor(el, model) { this.el = el; this.model = model; this.size_changed(); this.model.on('mask', this.mask_changed.bind(this)); this.model.on('image', this.image_changed.bind(this)); this.model.on('vanishing', this.mask_changed.bind(this)); this.model.on('width', this.size_changed.bind(this)); this.model.on('height', this.size_changed.bind(this)); } mouse_stroke(first_event) { var self = this; if (first_event.which === 3 || first_event.button === 2) { first_event.preventDefault(); self.mask_canvas.style.pointerEvents = 'none'; setTimeout(() => { self.mask_canvas.style.pointerEvents = 'all'; }, 3000); return; } if (self.model.get('disabled')) { return; } if (self.model.get('oneshot')) { var canvas = self.mask_canvas; var ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, canvas.width, canvas.height); } function track_mouse(evt) { if (evt.type == 'keydown' || self.model.get('disabled')) { if (self.model.get('disabled') || evt.key === "Escape") { window.removeEventListener('mousemove', track_mouse); window.removeEventListener('mouseup', track_mouse); window.removeEventListener('keydown', track_mouse, true); if (self.model.get('dragging')) { self.model.set('dragging', false); } self.mask_changed(); } return; } if (evt.type == 'mouseup' || (typeof evt.buttons != 'undefined' && evt.buttons == 0)) { window.removeEventListener('mousemove', track_mouse); window.removeEventListener('mouseup', track_mouse); window.removeEventListener('keydown', track_mouse, true); self.model.set('dragging', false); self.model.set('mask', self.mask_canvas.toDataURL()); return; } var p = self.cursor_position(evt); var d = self.model.get('dragging'); var e = self.model.get('erase') ^ (evt.ctrlKey); if (!d) { self.model.set('startpos', [p.x, p.y]); } self.model.set('dragpos', [p.x, p.y]); if (!d) { self.model.set('dragging', true); } self.fill_circle(p.x, p.y, self.model.get('brushsize'), e); } this.mask_canvas.focus(); window.addEventListener('mousemove', track_mouse); window.addEventListener('mouseup', track_mouse); window.addEventListener('keydown', track_mouse, true); track_mouse(first_event); } mask_changed() { this.mask_canvas.classList.toggle("vanishing", this.model.get('vanishing')); this.draw_data_url(this.mask_canvas, this.model.get('mask')); } image_changed() { this.image.src = this.model.get('image'); } size_changed() { this.mask_canvas = document.createElement('canvas'); this.image = document.createElement('img'); this.mask_canvas.className = "paintmask"; this.image.className = "paintimage"; for (var attr of ['width', 'height']) { this.mask_canvas[attr] = this.model.get(attr); this.image[attr] = this.model.get(attr); } this.el.innerHTML = ''; this.el.appendChild(this.image); this.el.appendChild(this.mask_canvas); this.mask_canvas.addEventListener('mousedown', this.mouse_stroke.bind(this)); this.mask_changed(); this.image_changed(); } cursor_position(evt) { const rect = this.mask_canvas.getBoundingClientRect(); const x = event.clientX - rect.left; const y = event.clientY - rect.top; return {x: x, y: y}; } fill_circle(x, y, r, erase, blur) { var ctx = this.mask_canvas.getContext('2d'); ctx.save(); if (blur) { ctx.filter = 'blur(' + blur + 'px)'; } ctx.globalCompositeOperation = ( erase ? "destination-out" : 'source-over'); ctx.fillStyle = '#fff'; ctx.beginPath(); ctx.arc(x, y, r, 0, 2 * Math.PI); ctx.fill(); ctx.restore() } draw_data_url(canvas, durl) { var ctx = canvas.getContext('2d'); var img = new Image; canvas.pendingImg = img; function imgdone() { if (canvas.pendingImg == img) { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.drawImage(img, 0, 0); canvas.pendingImg = null; } } img.addEventListener('load', imgdone); img.addEventListener('error', imgdone); img.src = durl; } } """