diff --git a/core/modules/new_widgets/edit-bitmap.js b/core/modules/new_widgets/edit-bitmap.js new file mode 100644 index 000000000..caa7c971b --- /dev/null +++ b/core/modules/new_widgets/edit-bitmap.js @@ -0,0 +1,313 @@ +/*\ +title: $:/core/modules/new_widgets/edit-bitmap.js +type: application/javascript +module-type: new_widget + +Edit-bitmap widget + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +// Default image sizes +var DEFAULT_IMAGE_WIDTH = 300, + DEFAULT_IMAGE_HEIGHT = 185; + +var Widget = require("$:/core/modules/new_widgets/widget.js").widget; + +var EditBitmapWidget = function(parseTreeNode,options) { + this.initialise(parseTreeNode,options); +}; + +/* +Inherit from the base widget class +*/ +EditBitmapWidget.prototype = new Widget(); + +/* +Render this widget into the DOM +*/ +EditBitmapWidget.prototype.render = function(parent,nextSibling) { + var self = this; + // Save the parent dom node + this.parentDomNode = parent; + // Compute our attributes + this.computeAttributes(); + // Execute our logic + this.execute(); + // Create our element + this.canvasDomNode = $tw.utils.domMaker("canvas",{ + document: this.document, + "class":"tw-edit-bitmapeditor", + eventListeners: [{ + name: "touchstart", handlerObject: this, handlerMethod: "handleTouchStartEvent" + },{ + name: "touchmove", handlerObject: this, handlerMethod: "handleTouchMoveEvent" + },{ + name: "touchend", handlerObject: this, handlerMethod: "handleTouchEndEvent" + },{ + name: "mousedown", handlerObject: this, handlerMethod: "handleMouseDownEvent" + },{ + name: "mousemove", handlerObject: this, handlerMethod: "handleMouseMoveEvent" + },{ + name: "mouseup", handlerObject: this, handlerMethod: "handleMouseUpEvent" + }] + }); + this.widthDomNode = $tw.utils.domMaker("input",{ + document: this.document, + "class":"tw-edit-bitmapeditor-width", + eventListeners: [{ + name: "change", handlerObject: this, handlerMethod: "handleWidthChangeEvent" + }] + }); + this.heightDomNode = $tw.utils.domMaker("input",{ + document: this.document, + "class":"tw-edit-bitmapeditor-height", + eventListeners: [{ + name: "change", handlerObject: this, handlerMethod: "handleHeightChangeEvent" + }] + }); + // Insert the elements into the DOM + parent.insertBefore(this.canvasDomNode,nextSibling); + parent.insertBefore(this.widthDomNode,nextSibling); + parent.insertBefore(this.heightDomNode,nextSibling); + this.domNodes.push(this.canvasDomNode,this.widthDomNode,this.heightDomNode); + // Load the image into the canvas + this.loadCanvas(); +}; + +/* +Compute the internal state of the widget +*/ +EditBitmapWidget.prototype.execute = function() { + // Get our parameters + this.editTitle = this.getAttribute("title",this.getVariable("tiddlerTitle")); + this.editClass = this.getAttribute("class"); +}; + +/* +Note that the bitmap editor intentionally doesn't try to refresh itself because it would be confusing to have the image changing spontaneously while editting it +*/ +EditBitmapWidget.prototype.refresh = function(changedTiddlers) { + return false; +}; + +/* +Remove any DOM nodes created by this widget or its children +*/ +EditBitmapWidget.prototype.removeChildDomNodes = function() { + $tw.utils.each(this.domNodes,function(domNode) { + domNode.parentNode.removeChild(domNode); + }); + this.domNodes = []; +}; + +EditBitmapWidget.prototype.loadCanvas = function() { + var tiddler = this.wiki.getTiddler(this.editTitle), + currImage = new Image(); + // Set up event handlers for loading the image + var self = this; + currImage.onload = function() { + // Copy the image to the on-screen canvas + self.initCanvas(self.canvasDomNode,currImage.width,currImage.height,currImage); + // And also copy the current bitmap to the off-screen canvas + self.currCanvas = self.document.createElement("canvas"); + self.initCanvas(self.currCanvas,currImage.width,currImage.height,currImage); + // Set the width and height input boxes + self.updateSize(); + }; + currImage.onerror = function() { + // Set the on-screen canvas size and clear it + self.initCanvas(self.canvasDomNode,DEFAULT_IMAGE_WIDTH,DEFAULT_IMAGE_HEIGHT); + // Set the off-screen canvas size and clear it + self.currCanvas = self.document.createElement("canvas"); + self.initCanvas(self.currCanvas,DEFAULT_IMAGE_WIDTH,DEFAULT_IMAGE_HEIGHT); + // Set the width and height input boxes + self.updateSize(); + } + // Get the current bitmap into an image object + currImage.src = "data:" + tiddler.fields.type + ";base64," + tiddler.fields.text; +}; + +EditBitmapWidget.prototype.initCanvas = function(canvas,width,height,image) { + canvas.width = width; + canvas.height = height; + var ctx = canvas.getContext("2d"); + if(image) { + ctx.drawImage(image,0,0); + } else { + ctx.fillStyle = "#fff"; + ctx.fillRect(0,0,canvas.width,canvas.height); + } +} + +/* +** Update the input boxes with the actual size of the canvas +*/ +EditBitmapWidget.prototype.updateSize = function() { + this.widthDomNode.value = this.currCanvas.width; + this.heightDomNode.value = this.currCanvas.height; +}; + +/* +** Change the size of the canvas, preserving the current image +*/ +EditBitmapWidget.prototype.changeCanvasSize = function(newWidth,newHeight) { + // Create and size a new canvas + var newCanvas = this.document.createElement("canvas"); + this.initCanvas(newCanvas,newWidth,newHeight); + // Copy the old image + var ctx = newCanvas.getContext("2d"); + ctx.drawImage(this.currCanvas,0,0); + // Set the new canvas as the current one + this.currCanvas = newCanvas; + // Set the size of the onscreen canvas + this.canvasDomNode.width = newWidth; + this.canvasDomNode.height = newHeight; + // Paint the onscreen canvas with the offscreen canvas + ctx = this.canvasDomNode.getContext("2d"); + ctx.drawImage(this.currCanvas,0,0); +}; + +EditBitmapWidget.prototype.handleWidthChangeEvent = function(event) { + // Get the new width + var newWidth = parseInt(this.widthDomNode.value,10); + // Update if necessary + if(newWidth > 0 && newWidth !== this.currCanvas.width) { + this.changeCanvasSize(newWidth,this.currCanvas.height); + } + // Update the input controls + this.updateSize(); +}; + +EditBitmapWidget.prototype.handleHeightChangeEvent = function(event) { + // Get the new width + var newHeight = parseInt(this.heightDomNode.value,10); + // Update if necessary + if(newHeight > 0 && newHeight !== this.currCanvas.height) { + this.changeCanvasSize(this.currCanvas.width,newHeight); + } + // Update the input controls + this.updateSize(); +}; + +EditBitmapWidget.prototype.handleTouchStartEvent = function(event) { + this.brushDown = true; + this.strokeStart(event.touches[0].clientX,event.touches[0].clientY); + event.preventDefault(); + event.stopPropagation(); + return false; +}; + +EditBitmapWidget.prototype.handleTouchMoveEvent = function(event) { + if(this.brushDown) { + this.strokeMove(event.touches[0].clientX,event.touches[0].clientY); + } + event.preventDefault(); + event.stopPropagation(); + return false; +}; + +EditBitmapWidget.prototype.handleTouchEndEvent = function(event) { + if(this.brushDown) { + this.brushDown = false; + this.strokeEnd(); + } + event.preventDefault(); + event.stopPropagation(); + return false; +}; + +EditBitmapWidget.prototype.handleMouseDownEvent = function(event) { + this.strokeStart(event.clientX,event.clientY); + this.brushDown = true; + event.preventDefault(); + event.stopPropagation(); + return false; +}; + +EditBitmapWidget.prototype.handleMouseMoveEvent = function(event) { + if(this.brushDown) { + this.strokeMove(event.clientX,event.clientY); + event.preventDefault(); + event.stopPropagation(); + return false; + } + return true; +}; + +EditBitmapWidget.prototype.handleMouseUpEvent = function(event) { + if(this.brushDown) { + this.brushDown = false; + this.strokeEnd(); + event.preventDefault(); + event.stopPropagation(); + return false; + } + return true; +}; + +EditBitmapWidget.prototype.adjustCoordinates = function(x,y) { + var canvasRect = this.canvasDomNode.getBoundingClientRect(), + scale = this.canvasDomNode.width/canvasRect.width; + return {x: (x - canvasRect.left) * scale, y: (y - canvasRect.top) * scale}; +}; + +EditBitmapWidget.prototype.strokeStart = function(x,y) { + // Start off a new stroke + this.stroke = [this.adjustCoordinates(x,y)]; +}; + +EditBitmapWidget.prototype.strokeMove = function(x,y) { + var ctx = this.canvasDomNode.getContext("2d"), + t; + // Add the new position to the end of the stroke + this.stroke.push(this.adjustCoordinates(x,y)); + // Redraw the previous image + ctx.drawImage(this.currCanvas,0,0); + // Render the stroke + ctx.strokeStyle = "#ff0"; + ctx.lineWidth = 3; + ctx.lineCap = "round"; + ctx.lineJoin = "round"; + ctx.beginPath(); + ctx.moveTo(this.stroke[0].x,this.stroke[0].y); + for(t=1; t;base64," + var dataURL = this.canvasDomNode.toDataURL(tiddler.fields.type,1.0), + posColon = dataURL.indexOf(":"), + posSemiColon = dataURL.indexOf(";"), + posComma = dataURL.indexOf(","), + type = dataURL.substring(posColon+1,posSemiColon), + text = dataURL.substring(posComma+1); + var update = {type: type, text: text}; + this.wiki.addTiddler(new $tw.Tiddler(tiddler,update)); + } +}; + +exports["edit-bitmap"] = EditBitmapWidget; + +})(); diff --git a/themes/tiddlywiki/snowwhite/base.tid b/themes/tiddlywiki/snowwhite/base.tid index 0abe08c69..91dd94651 100644 --- a/themes/tiddlywiki/snowwhite/base.tid +++ b/themes/tiddlywiki/snowwhite/base.tid @@ -527,11 +527,14 @@ a.tw-tiddlylink-external { } canvas.tw-edit-bitmapeditor { - border: 3px dashed #F0F0F0; + border: 6px solid #fff; cursor: crosshair; -moz-user-select: none; -webkit-user-select: none; -ms-user-select: none; + <> + margin-top: 6px; + margin-bottom: 6px; } .tw-edit-bitmapeditor-width {