diff options
Diffstat (limited to 'static/js/wpaint/src')
-rw-r--r-- | static/js/wpaint/src/wPaint.css | 348 | ||||
-rw-r--r-- | static/js/wpaint/src/wPaint.js | 1181 | ||||
-rw-r--r-- | static/js/wpaint/src/wPaint.utils.js | 70 |
3 files changed, 1599 insertions, 0 deletions
diff --git a/static/js/wpaint/src/wPaint.css b/static/js/wpaint/src/wPaint.css new file mode 100644 index 0000000..7bc0a70 --- /dev/null +++ b/static/js/wpaint/src/wPaint.css @@ -0,0 +1,348 @@ +/********************************************************************** + * Layout + **********************************************************************/ + +/*** menu ***/ +.wPaint-menu { + position: absolute !important; + display: inline-block; + line-height: 0px; + z-index: 99; +} +.wPaint-menu-behind { + z-index: 98; +} +.wPaint-menu-holder { + position: relative; + margin: 0 1px 1px 0; +} +.wPaint-menu-handle { + display: inline-block; +} +.wPaint-menu-icon { + position: relative; + vertical-align: top; +} +.wPaint-menu-icon-img { + position: relative; + display: inline-block; + background-repeat: no-repeat; + overflow: hidden; +} +/*** select ***/ +.wPaint-menu-select-holder{ + position: absolute; + left: 1px; + z-index: 10; + overflow: hidden; +} +.wPaint-menu-select { + position: relative; + text-align: center; + overflow-y: scroll; + z-index: 100; +} +.wPaint-menu-select-option.first { + border-top: 0px; +} +/*** alignment ***/ +.wPaint-menu-alignment-horizontal .wPaint-menu-icon { + display: inline-block; +} +.wPaint-menu-alignment-vertical .wPaint-menu-icon { + display: block; +} +/*** status ***/ +.wPaint-status { + position: absolute; + display: none; + right: 0px; + bottom: 0px; +} +/*** modal ***/ +.wPaint-modal-bg { + position: absolute; + left: 0px; + top: 0px; + width: 100%; + height: 100%; +} +.wPaint-modal { + position: absolute; + display: inline-block; +} +.wPaint-modal-holder { + display: inline-block; + overflow: hidden; +} +.wPaint-modal-content { + overflow-y: scroll; + width: 100%; + height: 100%; +} +.wPaint-modal-close { + position: absolute; +} +/*** text input ***/ +.wPaint-text-input{ + margin: 0px; + padding: 0px; + outline-width: 0; + word-wrap: break-word; + overflow: hidden; +} +/*** file load ***/ +.wPaint-modal-img-holder { + line-height: 0px; +} +.wPaint-modal-img { + display: inline-block; +} + +/********************************************************************** + * Generic Appearance + * + * Probably don't need to change these styles but can overwrite + * whatever is necessary. + **********************************************************************/ + +/*** menu ***/ +.wPaint-menu-holder { + border-style: solid; + border-width: 1px; + box-shadow:3px 3px 5px #555555; +} +.wPaint-menu-handle { + cursor: pointer; +} +.wPaint-menu-icon { + border-style: solid; + border-width: 1px; + cursor: pointer; +} +.wPaint-menu-icon.disabled { + cursor: default; +} +.wPaint-menu-icon.disabled .wPaint-menu-icon-img { + opacity: 0.3; +} +.wPaint-menu-icon-img { + font-family: verdana; + font-weight: bold; + text-align: center; +} +/*** select ***/ +.wPaint-menu-select-holder { + border-style: solid; + border-width: 1px; + box-shadow: 1px 1px 2px #666; +} +.wPaint-menu-select { + font-family: verdana; + text-align: center; +} +.wPaint-menu-select-option { + border-top-style: solid; + border-top-width: 1px; + cursor: pointer; +} +.wPaint-menu-icon-select-img { + background-repeat: no-repeat; +} +.wPaint-menu-icon-group-arrow { + position: absolute; + right: 1px; + bottom: 1px; +} +/*** alignment ***/ +.wPaint-menu-alignment-horizontal .wPaint-menu-handle { + border-right-style: solid; + border-right-width: 1px; +} +.wPaint-menu-alignment-vertical .wPaint-menu-handle { + border-bottom-style: solid; + border-bottom-width: 1px; +} +/*** status ***/ +.wPaint-status { + font-size: 10px; + font-family: verdana; + line-height: 10px; + height: 10px; + background-color: #3a3a3a; + color: #f0f0f0; + padding: 5px; + opacity: 0.5; +} +/*** modal ***/ +.wPaint-modal-bg { + background-color: #3a3a3a; + opacity: 0.8; +} +.wPaint-modal-holder { + height: 100px; + box-shadow: 3px 3px 5px #555555; + border-radius: 5px; + border-style: solid; + border-width: 2px; + cursor: default; +} +.wPaint-modal-close { + right: -7px; + top: -7px; + border-radius: 10px; + font-size: 8px; + line-height: 14px; + padding: 0 4px; + font-weight:bold; + border-style: solid; + border-width: 2px; + cursor: pointer; +} +/*** text input ***/ +.wPaint-text-input{ + border: dotted #0000FF 1px; + background: none; +} +/*** file load ***/ +.wPaint-modal-img-holder { + border: solid #333 1px; + border-radius: 5px; + margin: 3px; + padding: 2px; + cursor: pointer; +} +.wPaint-modal-img { + width: 100px; + border-radius: 4px; + margin-bottom: 0px; +} + +/********************************************************************** + * Size - standard theme + **********************************************************************/ + +/*** menu ***/ +.wPaint-theme-standard .wPaint-menu-holder { + border-radius: 7px; +} +.wPaint-theme-standard .wPaint-menu-select-holder { + border-radius: 5px; +} +.wPaint-theme-standard .wPaint-menu-icon { + border-radius: 7px; +} +.wPaint-theme-standard .wPaint-menu-icon-img { + margin: 6px 5px 5px 6px; + width: 18px; + height: 18px; + line-height: 18px; + font-size: 12px; +} +.wPaint-theme-standard .wPaint-menu-colorpicker .wPaint-menu-icon-img { + margin: 3px 2px 2px 3px; + width: 24px; + height: 24px; + border-radius: 5px; +} +/*** select ***/ +.wPaint-theme-standard .wPaint-menu-icon-group .wPaint-menu-select-option { + padding: 4px; +} +.wPaint-theme-standard .wPaint-menu-icon-group-arrow { + width: 5px; + height: 3px; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAADCAYAAABbNsX4AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAK6wAACusBgosNWgAAABZ0RVh0Q3JlYXRpb24gVGltZQAwOC8xMS8xMyj8hykAAAAcdEVYdFNvZnR3YXJlAEFkb2JlIEZpcmV3b3JrcyBDUzbovLKMAAAAKElEQVQImV3IwQ0AMAyDQKerspZ3pa9IVXmdGMB8nbbzjrYTNWoA1xeQ3RPyxUyE/gAAAABJRU5ErkJggg=='); +} +.wPaint-theme-standard .wPaint-menu-select { + line-height: 10px; + font-size: 10px; + max-height: 136px; +} +.wPaint-theme-standard .wPaint-menu-select-option { + max-width: 50px; + padding: 4px 7px; +} +.wPaint-theme-standard .wPaint-menu-icon-select-img { + width: 18px; + height: 18px; +} +/* horizontal */ +.wPaint-theme-standard .wPaint-menu-alignment-horizontal.wPaint-menu-nohandle .wPaint-menu-holder { + padding-left: 4px; +} +.wPaint-theme-standard .wPaint-menu-alignment-horizontal .wPaint-menu-icon { + margin: 4px 5px 4px 0; +} +.wPaint-theme-standard .wPaint-menu-alignment-horizontal .wPaint-menu-handle { + width: 30px; + height: 39px; + margin-right: 5px; + border-top-left-radius: 7px; + border-bottom-left-radius: 7px; +} +/* vertical */ +.wPaint-theme-standard .wPaint-menu-alignment-vertical.wPaint-menu-nohandle .wPaint-menu-holder { + padding-top: 4px; +} +.wPaint-theme-standard .wPaint-menu-alignment-vertical .wPaint-menu-icon { + margin: 0 4px 5px 4px; +} +.wPaint-theme-standard .wPaint-menu-alignment-vertical .wPaint-menu-handle { + width: 39px; + height: 30px; + margin-bottom: 5px; + border-top-left-radius: 7px; + border-top-right-radius: 7px; +} + +/********************************************************************** + * Style - classic theme + **********************************************************************/ + +/*** menu ***/ +.wPaint-theme-classic .wPaint-menu-holder { + border-color: #dadada; + background-color: #f0f0f0; +} +.wPaint-theme-classic .wPaint-menu-handle { + background-color: #dadada; + box-shadow: inset 1px 1px 3px #FFF; + border-color: #dadada; +} +.wPaint-theme-classic .wPaint-menu-icon { + border-color: #b9b9b9; + background-color: #b9b9b9; + box-shadow: inset 2px 2px 3px #eee, 1px 1px 2px #666; +} +.wPaint-theme-classic .wPaint-menu-icon.hover, +.wPaint-theme-classic .wPaint-menu-icon.active { + border-color: #99ccff; + background-color: #aaccff; +} +.wPaint-theme-classic .wPaint-menu-icon-img { + color: #696969; +} +/*** select ***/ +.wPaint-theme-classic .wPaint-menu-select-holder { + border-color: #CACACA; +} +.wPaint-theme-classic .wPaint-menu-select { + color: #494949; +} +.wPaint-theme-classic .wPaint-menu-select-option { + box-shadow: inset 2px 2px 3px #fff; + border-top-color: #CACACA; + background-color: #F0F0F0; +} +.wPaint-theme-classic .wPaint-menu-select-option:hover { + box-shadow: inset 1px 1px 1px #fff; + background-color: #99ccff; + color: #f0f0f0; +} +/*** modal ***/ +.wPaint-theme-classic .wPaint-modal-close, +.wPaint-theme-classic .wPaint-modal-holder { + border-color: #3a3a3a; + background-color: #f0f0f0; +} diff --git a/static/js/wpaint/src/wPaint.js b/static/js/wpaint/src/wPaint.js new file mode 100644 index 0000000..9717f6d --- /dev/null +++ b/static/js/wpaint/src/wPaint.js @@ -0,0 +1,1181 @@ +(function ($) { + 'use strict'; + + /************************************************************************ + * Paint class + ************************************************************************/ + function Paint(el, options) { + this.$el = $(el); + this.options = options; + this.init = false; + + this.menus = {primary: null, active: null, all: {}}; + this.previousMode = null; + this.width = this.$el.width(); + this.height = this.$el.height(); + + this.ctxBgResize = false; + this.ctxResize = false; + + this.generate(); + this._init(); + } + + Paint.prototype = { + generate: function () { + if (this.init) { return this; } + + var _this = this; + + // automatically appends each canvas + // also returns the jQuery object so we can chain events right off the function call. + // for the tempCanvas we will be setting some extra attributes but don't won't matter + // as they will be reset on mousedown anyway. + function createCanvas(name) { + var newName = (name ? name.capitalize() : ''), + canvasName = 'canvas' + newName, + ctxName = 'ctx' + newName; + + _this[canvasName] = document.createElement('canvas'); + _this[ctxName] = _this[canvasName].getContext('2d'); + _this['$' + canvasName] = $(_this[canvasName]); + + _this['$' + canvasName] + .attr('class', 'wPaint-canvas' + (name ? '-' + name : '')) + .attr('width', _this.width + 'px') + .attr('height', _this.height + 'px') + .css({position: 'absolute', left: 0, top: 0}); + + _this.$el.append(_this['$' + canvasName]); + + return _this['$' + canvasName]; + } + + // event functions + function canvasMousedown(e) { + e.preventDefault(); + e.stopPropagation(); + _this.draw = true; + e.canvasEvent = 'down'; + _this._closeSelectBoxes(); + _this._callShapeFunc.apply(_this, [e]); + } + + function documentMousemove(e) { + if (_this.draw) { + e.canvasEvent = 'move'; + _this._callShapeFunc.apply(_this, [e]); + } + } + + function documentMouseup(e) { + + //make sure we are in draw mode otherwise this will fire on any mouse up. + if (_this.draw) { + _this.draw = false; + e.canvasEvent = 'up'; + _this._callShapeFunc.apply(_this, [e]); + } + } + + // create bg canvases + createCanvas('bg'); + + // create drawing canvas + createCanvas('') + .on('mousedown', canvasMousedown) + .bindMobileEvents(); + + // create temp canvas for drawing shapes temporarily + // before transfering to main canvas + createCanvas('temp').hide(); + + // event handlers for drawing + $(document) + .on('mousemove', documentMousemove) + .on('mousedown', $.proxy(this._closeSelectBoxes, this)) + .on('mouseup', documentMouseup); + + // we will need to preset theme to get proper dimensions + // when creating menus which will be appended after this + this.setTheme(this.options.theme); + }, + + _init: function () { + var index = null, + setFuncName = null; + + this.init = true; + + // run any set functions if they exist + for (index in this.options) { + setFuncName = 'set' + index.capitalize(); + if (this[setFuncName]) { this[setFuncName](this.options[index]); } + } + + // fix menus + this._fixMenus(); + + // initialize active menu button + this.menus.primary._getIcon(this.options.mode).trigger('click'); + }, + + resize: function () { + var bg = this.getBg(), + image = this.getImage(); + + this.width = this.$el.width(); + this.height = this.$el.height(); + + this.canvasBg.width = this.width; + this.canvasBg.height = this.height; + this.canvas.width = this.width; + this.canvas.height = this.height; + + if (this.ctxBgResize === false) { + this.ctxBgResize = true; + this.setBg(bg, true); + } + + if (this.ctxResize === false) { + this.ctxResize = true; + this.setImage(image, '', true, true); + } + }, + + /************************************ + * setters + ************************************/ + setTheme: function (theme) { + var i, ii; + + theme = theme.split(' '); + + // remove anything beginning with "wPaint-theme-" first + this.$el.attr('class', (this.$el.attr('class') || '').replace(/wPaint-theme-.+\s|wPaint-theme-.+$/, '')); + + // add each theme + for (i = 0, ii = theme.length; i < ii; i++) { + this.$el.addClass('wPaint-theme-' + theme[i]); + } + }, + + setMode: function (mode) { + this.setCursor(mode); + this.previousMode = this.options.mode; + this.options.mode = mode; + }, + + setImage: function (img, ctxType, resize, notUndo) { + if (!img) { return true; } + + var _this = this, + myImage = null, + ctx = ''; + + function loadImage() { + var ratio = 1, xR = 0, yR = 0, x = 0, y = 0, w = myImage.width, h = myImage.height; + + if (!resize) { + // get width/height + if (myImage.width > _this.width || myImage.height > _this.height || _this.options.imageStretch) { + xR = _this.width / myImage.width; + yR = _this.height / myImage.height; + + ratio = xR < yR ? xR : yR; + + w = myImage.width * ratio; + h = myImage.height * ratio; + } + + // get left/top (centering) + x = (_this.width - w) / 2; + y = (_this.height - h) / 2; + } + + ctx.clearRect(0, 0, _this.width, _this.height); + ctx.drawImage(myImage, x, y, w, h); + + _this[ctxType + 'Resize'] = false; + + // Default is to run the undo. + // If it's not to be run set it the flag to true. + if (!notUndo) { + _this._addUndo(); + } + } + + ctxType = 'ctx' + (ctxType || '').capitalize(); + ctx = this[ctxType]; + + if (window.rgbHex(img)) { + ctx.clearRect(0, 0, this.width, this.height); + ctx.fillStyle = img; + ctx.rect(0, 0, this.width, this.height); + ctx.fill(); + } + else { + myImage = new Image(); + myImage.src = img.toString(); + $(myImage).load(loadImage); + } + }, + + setBg: function (img, resize) { + if (!img) { return true; } + + this.setImage(img, 'bg', resize, true); + }, + + setCursor: function (cursor) { + cursor = $.fn.wPaint.cursors[cursor] || $.fn.wPaint.cursors['default']; + + this.$el.css('cursor', 'url("' + this.options.path + cursor.path + '") ' + cursor.left + ' ' + cursor.top + ', default'); + }, + + setMenuOrientation: function (orientation) { + $.each(this.menus.all, function (i, menu) { + menu.options.aligment = orientation; + menu.setAlignment(orientation); + }); + }, + + getImage: function (withBg) { + var canvasSave = document.createElement('canvas'), + ctxSave = canvasSave.getContext('2d'); + + withBg = withBg === false ? false : true; + + $(canvasSave) + .css({display: 'none', position: 'absolute', left: 0, top: 0}) + .attr('width', this.width) + .attr('height', this.height); + + if (withBg) { ctxSave.drawImage(this.canvasBg, 0, 0); } + ctxSave.drawImage(this.canvas, 0, 0); + + return canvasSave.toDataURL(); + }, + + getBg: function () { + return this.canvasBg.toDataURL(); + }, + + /************************************ + * prompts + ************************************/ + _displayStatus: function (msg) { + var _this = this; + + if (!this.$status) { + this.$status = $('<div class="wPaint-status"></div>'); + this.$el.append(this.$status); + } + + this.$status.html(msg); + clearTimeout(this.displayStatusTimer); + + this.$status.fadeIn(500, function () { + _this.displayStatusTimer = setTimeout(function () { _this.$status.fadeOut(500); }, 1500); + }); + }, + + _showModal: function ($content) { + var _this = this, + $bg = this.$el.children('.wPaint-modal-bg'), + $modal = this.$el.children('.wPaint-modal'); + + function modalFadeOut() { + $bg.remove(); + $modal.remove(); + _this._createModal($content); + } + + if ($bg.length) { + $modal.fadeOut(500, modalFadeOut); + } + else { + this._createModal($content); + } + }, + + _createModal: function ($content) { + $content = $('<div class="wPaint-modal-content"></div>').append($content.children()); + + var $bg = $('<div class="wPaint-modal-bg"></div>'), + $modal = $('<div class="wPaint-modal"></div>'), + $holder = $('<div class="wPaint-modal-holder"></div>'), + $close = $('<div class="wPaint-modal-close">X</div>'); + + function modalClick() { + $modal.fadeOut(500, modalFadeOut); + } + + function modalFadeOut() { + $bg.remove(); + $modal.remove(); + } + + $close.on('click', modalClick); + $modal.append($holder.append($content)).append($close); + this.$el.append($bg).append($modal); + + $modal.css({ + left: (this.$el.outerWidth() / 2) - ($modal.outerWidth(true) / 2), + top: (this.$el.outerHeight() / 2) - ($modal.outerHeight(true) / 2) + }); + + $modal.fadeIn(500); + }, + + /************************************ + * menu helpers + ************************************/ + _createMenu: function (name, options) { + options = options || {}; + options.alignment = this.options.menuOrientation; + options.handle = this.options.menuHandle; + + return new Menu(this, name, options); + }, + + _fixMenus: function () { + var _this = this, + $selectHolder = null; + + function selectEach(i, el) { + var $el = $(el), + $select = $el.clone(); + + $select.appendTo(_this.$el); + + if ($select.outerHeight() === $select.get(0).scrollHeight) { + $el.css({overflowY: 'auto'}); + } + + $select.remove(); + } + + // TODO: would be nice to do this better way + // for some reason when setting overflowY:auto with dynamic content makes the width act up + for (var key in this.menus.all) { + $selectHolder = _this.menus.all[key].$menu.find('.wPaint-menu-select-holder'); + if ($selectHolder.length) { $selectHolder.children().each(selectEach); } + } + }, + + _closeSelectBoxes: function (item) { + var key, $selectBoxes; + + for (key in this.menus.all) { + $selectBoxes = this.menus.all[key].$menuHolder.children('.wPaint-menu-icon-select'); + + // hide any open select menus excluding the current menu + // this is to avoid the double toggle since there are some + // other events running here + if (item) { $selectBoxes = $selectBoxes.not('.wPaint-menu-icon-name-' + item.name); } + + $selectBoxes.children('.wPaint-menu-select-holder').hide(); + } + }, + + /************************************ + * events + ************************************/ + //_imageOnload: function () { + // /* a blank helper function for post image load calls on canvas - can be extended by other plugins using the setImage called */ + //}, + + _callShapeFunc: function (e) { + + // TODO: this is where issues with mobile offsets are probably off + var canvasOffset = this.$canvas.offset(), + canvasEvent = e.canvasEvent.capitalize(), + func = '_draw' + this.options.mode.capitalize() + canvasEvent; + + // update offsets here since we are detecting mouseup on $(document) not on the canvas + e.pageX = Math.floor(e.pageX - canvasOffset.left); + e.pageY = Math.floor(e.pageY - canvasOffset.top); + + // call drawing func + if (this[func]) { this[func].apply(this, [e]); } + + // run callback if set + if (this.options['draw' + canvasEvent]) { this.options['_draw' + canvasEvent].apply(this, [e]); } + + // run options (user) callback if set + if (canvasEvent === 'Down' && this.options.onShapeDown) { this.options.onShapeDown.apply(this, [e]); } + else if (canvasEvent === 'Move' && this.options.onShapeMove) { this.options.onShapeMove.apply(this, [e]); } + else if (canvasEvent === 'Up' && this.options.onShapeUp) { this.options.onShapeUp.apply(this, [e]); } + }, + + _stopPropagation: function (e) { + e.stopPropagation(); + }, + + /************************************ + * shape helpers + ************************************/ + _drawShapeDown: function (e) { + this.$canvasTemp + .css({left: e.PageX, top: e.PageY}) + .attr('width', 0) + .attr('height', 0) + .show(); + + this.canvasTempLeftOriginal = e.pageX; + this.canvasTempTopOriginal = e.pageY; + }, + + _drawShapeMove: function (e, factor) { + var xo = this.canvasTempLeftOriginal, + yo = this.canvasTempTopOriginal; + + // we may need these in other funcs, so we'll just pass them along with the event + factor = factor || 2; + e.left = (e.pageX < xo ? e.pageX : xo); + e.top = (e.pageY < yo ? e.pageY : yo); + e.width = Math.abs(e.pageX - xo); + e.height = Math.abs(e.pageY - yo); + e.x = this.options.lineWidth / 2 * factor; + e.y = this.options.lineWidth / 2 * factor; + e.w = e.width - this.options.lineWidth * factor; + e.h = e.height - this.options.lineWidth * factor; + + $(this.canvasTemp) + .css({left: e.left, top: e.top}) + .attr('width', e.width) + .attr('height', e.height); + + // store these for later to use in our "up" call + this.canvasTempLeftNew = e.left; + this.canvasTempTopNew = e.top; + + factor = factor || 2; + + // TODO: set this globally in _drawShapeDown (for some reason colors are being reset due to canvas resize - is there way to permanently set it) + this.ctxTemp.fillStyle = this.options.fillStyle; + this.ctxTemp.strokeStyle = this.options.strokeStyle; + this.ctxTemp.lineWidth = this.options.lineWidth * factor; + }, + + _drawShapeUp: function () { + this.ctx.drawImage(this.canvasTemp, this.canvasTempLeftNew, this.canvasTempTopNew); + this.$canvasTemp.hide(); + }, + + /**************************************** + * dropper + ****************************************/ + _drawDropperDown: function (e) { + var pos = {x: e.pageX, y: e.pageY}, + pixel = this._getPixel(this.ctx, pos), + color = null; + + // if we get no color try getting from the background + //if(pixel.r === 0 && pixel.g === 0 && pixel.b === 0 && pixel.a === 0) { + // imageData = this.ctxBg.getImageData(0, 0, this.width, this.height) + // pixel = this._getPixel(imageData, pos); + //} + + color = 'rgba(' + [ pixel.r, pixel.g, pixel.b, pixel.a ].join(',') + ')'; + + // set color from dropper here + this.options[this.dropper] = color; + this.menus.active._getIcon(this.dropper).wColorPicker('color', color); + }, + + _drawDropperUp: function () { + this.setMode(this.previousMode); + }, + + // get pixel data represented as RGBa color from pixel array. + _getPixel: function (ctx, pos) { + var imageData = ctx.getImageData(0, 0, this.width, this.height), + pixelArray = imageData.data, + base = ((pos.y * imageData.width) + pos.x) * 4; + + return { + r: pixelArray[base], + g: pixelArray[base + 1], + b: pixelArray[base + 2], + a: pixelArray[base + 3] + }; + } + }; + + /************************************************************************ + * Menu class + ************************************************************************/ + function Menu(wPaint, name, options) { + this.wPaint = wPaint; + this.options = options; + this.name = name; + this.type = !wPaint.menus.primary ? 'primary' : 'secondary'; + this.docked = true; + this.dockOffset = {left: 0, top: 0}; + + this.generate(); + } + + Menu.prototype = { + generate: function () { + this.$menu = $('<div class="wPaint-menu"></div>'); + this.$menuHolder = $('<div class="wPaint-menu-holder wPaint-menu-name-' + this.name + '"></div>'); + + if (this.options.handle) { this.$menuHandle = this._createHandle(); } + else { this.$menu.addClass('wPaint-menu-nohandle'); } + + if (this.type === 'primary') { + + // store the primary menu in primary object - we will need this reference later + this.wPaint.menus.primary = this; + + this.setOffsetLeft(this.options.offsetLeft); + this.setOffsetTop(this.options.offsetTop); + } + else if (this.type === 'secondary') { + this.$menu.hide(); + } + + // append menu items + this.$menu.append(this.$menuHolder.append(this.$menuHandle)); + this.reset(); + + // append menu + this.wPaint.$el.append(this.$menu); + + this.setAlignment(this.options.alignment); + }, + + // create / reset menu - will add new entries in the array + reset: function () { + var _this = this, + menu = $.fn.wPaint.menus[this.name], + key; + + // self invoking function + function itemAppend(item) { _this._appendItem(item); } + + for (key in menu.items) { + + // only add unique (new) items (icons) + if (!this.$menuHolder.children('.wPaint-menu-icon-name-' + key).length) { + + // add the item name, we will need this internally + menu.items[key].name = key; + + // use default img if img not set + menu.items[key].img = _this.wPaint.options.path + (menu.items[key].img || menu.img); + + // make self invoking to avoid overwrites + (itemAppend)(menu.items[key]); + } + } + }, + + _appendItem: function (item) { + var $item = this['_createIcon' + item.icon.capitalize()](item); + + if (item.after) { + this.$menuHolder.children('.wPaint-menu-icon-name-' + item.after).after($item); + } + else { + this.$menuHolder.append($item); + } + }, + + /************************************ + * setters + ************************************/ + setOffsetLeft: function (left) { + this.$menu.css({left: left}); + }, + + setOffsetTop: function (top) { + this.$menu.css({top: top}); + }, + + setAlignment: function (alignment) { + var tempLeft = this.$menu.css('left'); + + this.$menu.attr('class', this.$menu.attr('class').replace(/wPaint-menu-alignment-.+\s|wPaint-menu-alignment-.+$/, '')); + this.$menu.addClass('wPaint-menu-alignment-' + alignment); + + this.$menu.width('auto').css('left', -10000); + this.$menu.width(this.$menu.width()).css('left', tempLeft); + + // set proper offsets based on alignment + if (this.type === 'secondary') { + if (this.options.alignment === 'horizontal') { + this.dockOffset.top = this.wPaint.menus.primary.$menu.outerHeight(true); + } + else { + this.dockOffset.left = this.wPaint.menus.primary.$menu.outerWidth(true); + } + } + }, + + /************************************ + * handle + ************************************/ + _createHandle: function () { + var _this = this, + $handle = $('<div class="wPaint-menu-handle"></div>'); + + // draggable functions + function draggableStart() { + _this.docked = false; + _this._setDrag(); + } + + function draggableStop() { + $.each(_this.$menu.data('ui-draggable').snapElements, function (i, el) { + var offset = _this.$menu.offset(), + offsetPrimary = _this.wPaint.menus.primary.$menu.offset(); + + _this.dockOffset.left = offset.left - offsetPrimary.left; + _this.dockOffset.top = offset.top - offsetPrimary.top; + _this.docked = el.snapping; + }); + + _this._setDrag(); + } + + function draggableDrag() { + _this._setIndex(); + } + + // the drag/snap events for menus are tricky + // init handle for ALL menus, primary menu will drag a secondary menu with it, but that is un/binded in the toggle function + this.$menu.draggable({handle: $handle}); + + // if it's a secondary menu we want to check for snapping + // on drag we set docked to false, on snap we set it back to true + if (this.type === 'secondary') { + this.$menu.draggable('option', 'snap', this.wPaint.menus.primary.$menu); + this.$menu.draggable('option', 'start', draggableStart); + this.$menu.draggable('option', 'stop', draggableStop); + this.$menu.draggable('option', 'drag', draggableDrag); + } + + $handle.bindMobileEvents(); + + return $handle; + }, + + /************************************ + * generic icon + ************************************/ + _createIconBase: function (item) { + var _this = this, + $icon = $('<div class="wPaint-menu-icon wPaint-menu-icon-name-' + item.name + '"></div>'), + $iconImg = $('<div class="wPaint-menu-icon-img"></div>'), + width = $iconImg.realWidth(null, null, this.wPaint.$el); + + function mouseenter(e) { + var $el = $(e.currentTarget); + + $el.siblings('.hover').removeClass('hover'); + if (!$el.hasClass('disabled')) { $el.addClass('hover'); } + } + + function mouseleave(e) { + $(e.currentTarget).removeClass('hover'); + } + + function click() { + _this.wPaint.menus.active = _this; + } + + $icon + .attr('title', item.title) + .on('mousedown', $.proxy(this.wPaint._closeSelectBoxes, this.wPaint, item)) + .on('mouseenter', mouseenter) + .on('mouseleave', mouseleave) + .on('click', click); + + // can have index:0 so be careful here + if ($.isNumeric(item.index)) { + $iconImg + .css({ + backgroundImage: 'url(' + item.img + ')', + backgroundPosition: (-width * item.index) + 'px 0px' + }); + } + + return $icon.append($iconImg); + }, + + /************************************ + * icon group + ************************************/ + _createIconGroup: function (item) { + var _this = this, + css = {backgroundImage: 'url(' + item.img + ')'}, + $icon = this.$menuHolder.children('.wPaint-menu-icon-group-' + item.group), + iconExists = $icon.length, + $selectHolder = null, + $option = null, + $item = null, + width = 0; + + // local functions + function setIconClick() { + + // only trigger if menu is not visible otherwise it will fire twice + // from the mousedown to open the menu which we want just to display the menu + // not fire the button callback + if (!$icon.children('.wPaint-menu-select-holder').is(':visible')) { + item.callback.apply(_this.wPaint, []); + } + } + + function selectHolderClick() { + $icon.addClass('active').siblings('.active').removeClass('active'); + } + + function optionClick() { + + // rebind the main icon when we select an option + $icon + .attr('title', item.title) + .off('click.setIcon') + .on('click.setIcon', setIconClick); + + // run the callback right away when we select an option + $icon.children('.wPaint-menu-icon-img').css(css); + item.callback.apply(_this.wPaint, []); + } + + // crate icon if it doesn't exist yet + if (!iconExists) { + $icon = this._createIconBase(item) + .addClass('wPaint-menu-icon-group wPaint-menu-icon-group-' + item.group) + .on('click.setIcon', setIconClick) + .on('mousedown', $.proxy(this._iconClick, this)); + } + + // get the proper width here now that we have the icon + // this is for the select box group not the main icon + width = $icon.children('.wPaint-menu-icon-img').realWidth(null, null, this.wPaint.$el); + css.backgroundPosition = (-width * item.index) + 'px center'; + + // create selectHolder if it doesn't exist + $selectHolder = $icon.children('.wPaint-menu-select-holder'); + if (!$selectHolder.length) { + $selectHolder = this._createSelectBox($icon); + $selectHolder.children().on('click', selectHolderClick); + } + + $item = $('<div class="wPaint-menu-icon-select-img"></div>') + .attr('title', item.title) + .css(css); + + $option = this._createSelectOption($selectHolder, $item) + .addClass('wPaint-menu-icon-name-' + item.name) + .on('click', optionClick); + + // move select option into place if after is set + if (item.after) { + $selectHolder.children('.wPaint-menu-select').children('.wPaint-menu-icon-name-' + item.after).after($option); + } + + // we only want to return an icon to append on the first run of a group + if (!iconExists) { return $icon; } + }, + + /************************************ + * icon generic + ************************************/ + _createIconGeneric: function (item) { + + // just a go between for the iconGeneric type + return this._createIconActivate(item); + }, + + /************************************ + * icon + ************************************/ + _createIconActivate: function (item) { + + // since we are piggy backing icon with the item.group + // we'll just do a redirect and keep the code separate for group icons + if (item.group) { return this._createIconGroup(item); } + + var _this = this, + $icon = this._createIconBase(item); + + function iconClick(e) { + if (item.icon !== 'generic') { _this._iconClick(e); } + item.callback.apply(_this.wPaint, [e]); + } + + $icon.on('click', iconClick); + + return $icon; + }, + + _isIconDisabled: function (name) { + return this.$menuHolder.children('.wPaint-menu-icon-name-' + name).hasClass('disabled'); + }, + + _setIconDisabled: function (name, disabled) { + var $icon = this.$menuHolder.children('.wPaint-menu-icon-name-' + name); + + if (disabled) { + $icon.addClass('disabled').removeClass('hover'); + } + else { + $icon.removeClass('disabled'); + } + }, + + _getIcon: function (name) { + return this.$menuHolder.children('.wPaint-menu-icon-name-' + name); + }, + + _iconClick: function (e) { + var $el = $(e.currentTarget), + menus = this.wPaint.menus.all; + + // make sure to loop using parent object - don't use .wPaint-menu-secondary otherwise we would hide menu for all canvases + for (var menu in menus) { + if (menus[menu] && menus[menu].type === 'secondary') { menus[menu].$menu.hide(); } + } + + $el.siblings('.active').removeClass('active'); + if (!$el.hasClass('disabled')) { $el.addClass('active'); } + }, + + /************************************ + * iconToggle + ************************************/ + _createIconToggle: function (item) { + var _this = this, + $icon = this._createIconBase(item); + + function iconClick() { + $icon.toggleClass('active'); + item.callback.apply(_this.wPaint, [$icon.hasClass('active')]); + } + + $icon.on('click', iconClick); + + return $icon; + }, + + /************************************ + * select + ************************************/ + _createIconSelect: function (item) { + var _this = this, + $icon = this._createIconBase(item), + $selectHolder = this._createSelectBox($icon), + i, ii, $option; + + function optionClick(e) { + $icon.children('.wPaint-menu-icon-img').html($(e.currentTarget).html()); + item.callback.apply(_this.wPaint, [$(e.currentTarget).html()]); + } + + // add values for select + for (i = 0, ii = item.range.length; i < ii; i++) { + $option = this._createSelectOption($selectHolder, item.range[i]); + $option.on('click', optionClick); + if (item.useRange) { $option.css(item.name, item.range[i]); } + } + + return $icon; + }, + + _createSelectBox: function ($icon) { + var $selectHolder = $('<div class="wPaint-menu-select-holder"></div>'), + $select = $('<div class="wPaint-menu-select"></div>'), + timer = null; + + function clickSelectHolder(e) { + e.stopPropagation(); + $selectHolder.hide(); + } + + function iconMousedown() { + timer = setTimeout(function () { $selectHolder.toggle(); }, 200); + } + + function iconMouseup() { + clearTimeout(timer); + } + + function iconClick() { + $selectHolder.toggle(); + } + + $selectHolder + .on('mousedown mouseup', this.wPaint._stopPropagation) + .on('click', clickSelectHolder) + .hide(); + + // of hozizontal we'll pop below the icon + if (this.options.alignment === 'horizontal') { + $selectHolder.css({left: 0, top: $icon.children('.wPaint-menu-icon-img').realHeight('outer', true, this.wPaint.$el)}); + } + // vertical we'll pop to the right + else { + $selectHolder.css({left: $icon.children('.wPaint-menu-icon-img').realWidth('outer', true, this.wPaint.$el), top: 0}); + } + + $icon + .addClass('wPaint-menu-icon-select') + .append('<div class="wPaint-menu-icon-group-arrow"></div>') + .append($selectHolder.append($select)); + + // for groups we want to add a delay before the selectBox pops up + if ($icon.hasClass('wPaint-menu-icon-group')) { + $icon + .on('mousedown', iconMousedown) + .on('mouseup', iconMouseup); + } + else { $icon.on('click', iconClick); } + + return $selectHolder; + }, + + _createSelectOption: function ($selectHolder, value) { + var $select = $selectHolder.children('.wPaint-menu-select'), + $option = $('<div class="wPaint-menu-select-option"></div>').append(value); + + // set class for first item to remove any undesired styles like borders + if (!$select.children().length) { $option.addClass('first'); } + + $select.append($option); + + return $option; + }, + + _setSelectValue: function (icon, value) { + this._getIcon(icon).children('.wPaint-menu-icon-img').html(value); + }, + + /************************************ + * color picker + ************************************/ + _createIconColorPicker: function (item) { + var _this = this, + $icon = this._createIconBase(item); + + function iconClick() { + + // if we happen to click on this while in dropper mode just revert to previous + if (_this.wPaint.options.mode === 'dropper') { _this.wPaint.setMode(_this.wPaint.previousMode); } + } + + function iconOnSelect(color) { + item.callback.apply(_this.wPaint, [color]); + } + + function iconOnDropper() { + $icon.trigger('click'); + _this.wPaint.dropper = item.name; + _this.wPaint.setMode('dropper'); + } + + $icon + .on('click', iconClick) + .addClass('wPaint-menu-colorpicker') + .wColorPicker({ + mode: 'click', + generateButton: false, + dropperButton: true, + onSelect: iconOnSelect, + onDropper: iconOnDropper + }); + + return $icon; + }, + + _setColorPickerValue: function (icon, value) { + this._getIcon(icon).children('.wPaint-menu-icon-img').css('backgroundColor', value); + }, + + /************************************ + * menu toggle + ************************************/ + _createIconMenu: function (item) { + var _this = this, + $icon = this._createIconActivate(item); + + function iconClick() { + _this.wPaint.setCursor(item.name); + + // the items name here will be the menu name + var menu = _this.wPaint.menus.all[item.name]; + menu.$menu.toggle(); + if (_this.handle) { + menu._setDrag(); + } else { + menu._setPosition(); + } + } + + $icon.on('click', iconClick); + + return $icon; + }, + + // here we specify which menu will be dragged + _setDrag: function () { + var $menu = this.$menu, + drag = null, stop = null; + + if ($menu.is(':visible')) { + if (this.docked) { + + // make sure we are setting proper menu object here + drag = stop = $.proxy(this._setPosition, this); + this._setPosition(); + } + + // register drag/stop events + this.wPaint.menus.primary.$menu.draggable('option', 'drag', drag); + this.wPaint.menus.primary.$menu.draggable('option', 'stop', stop); + } + }, + + _setPosition: function () { + var offset = this.wPaint.menus.primary.$menu.position(); + + this.$menu.css({ + left: offset.left + this.dockOffset.left, + top: offset.top + this.dockOffset.top + }); + }, + + _setIndex: function () { + var primaryOffset = this.wPaint.menus.primary.$menu.offset(), + secondaryOffset = this.$menu.offset(); + + if ( + secondaryOffset.top < primaryOffset.top || + secondaryOffset.left < primaryOffset.left + ) { + this.$menu.addClass('wPaint-menu-behind'); + } + else { + this.$menu.removeClass('wPaint-menu-behind'); + } + } + }; + + /************************************************************************ + * wPaint + ************************************************************************/ + $.support.canvas = (document.createElement('canvas')).getContext; + + $.fn.wPaint = function (options, value) { + + function create() { + if (!$.support.canvas) { + $(this).html('Browser does not support HTML5 canvas, please upgrade to a more modern browser.'); + return false; + } + + return $.proxy(get, this)(); + } + + function get() { + var wPaint = $.data(this, 'wPaint'); + + if (!wPaint) { + wPaint = new Paint(this, $.extend(true, {}, options)); + $.data(this, 'wPaint', wPaint); + } + + return wPaint; + } + + function runOpts() { + var wPaint = $.data(this, 'wPaint'); + + if (wPaint) { + if (wPaint[options]) { wPaint[options].apply(wPaint, [value]); } + else if (value !== undefined) { + if (wPaint[func]) { wPaint[func].apply(wPaint, [value]); } + if (wPaint.options[options]) { wPaint.options[options] = value; } + } + else { + if (wPaint[func]) { values.push(wPaint[func].apply(wPaint, [value])); } + else if (wPaint.options[options]) { values.push(wPaint.options[options]); } + else { values.push(undefined); } + } + } + } + + if (typeof options === 'string') { + var values = [], + func = (value ? 'set' : 'get') + options.charAt(0).toUpperCase() + options.substring(1); + + this.each(runOpts); + + if (values.length) { return values.length === 1 ? values[0] : values; } + + return this; + } + + options = $.extend({}, $.fn.wPaint.defaults, options); + options.lineWidth = parseInt(options.lineWidth, 10); + options.fontSize = parseInt(options.fontSize, 10); + + return this.each(create); + }; + + /************************************************************************ + * extend + ************************************************************************/ + $.fn.wPaint.extend = function (funcs, protoType) { + var key; + + function elEach(func) { + if (protoType[func]) { + var tmpFunc = Paint.prototype[func], + newFunc = funcs[func]; + + protoType[func] = function () { + tmpFunc.apply(this, arguments); + newFunc.apply(this, arguments); + }; + } + else { + protoType[func] = funcs[func]; + } + } + + protoType = protoType === 'menu' ? Menu.prototype : Paint.prototype; + + for (key in funcs) { (elEach)(key); } + }; + + /************************************************************************ + * Init holders + ************************************************************************/ + $.fn.wPaint.menus = {}; + + $.fn.wPaint.cursors = {}; + + $.fn.wPaint.defaults = { + path: '/', // set absolute path for images and cursors + theme: 'standard classic', // set theme + autoScaleImage: true, // auto scale images to size of canvas (fg and bg) + autoCenterImage: true, // auto center images (fg and bg, default is left/top corner) + menuHandle: true, // setting to false will means menus cannot be dragged around + menuOrientation: 'horizontal', // menu alignment (horizontal,vertical) + menuOffsetLeft: 5, // left offset of primary menu + menuOffsetTop: 5, // top offset of primary menu + bg: null, // set bg on init + image: null, // set image on init + imageStretch: false, // stretch smaller images to full canvans dimensions + onShapeDown: null, // callback for draw down event + onShapeMove: null, // callback for draw move event + onShapeUp: null // callback for draw up event + }; +})(jQuery);
\ No newline at end of file diff --git a/static/js/wpaint/src/wPaint.utils.js b/static/js/wpaint/src/wPaint.utils.js new file mode 100644 index 0000000..7f1346a --- /dev/null +++ b/static/js/wpaint/src/wPaint.utils.js @@ -0,0 +1,70 @@ +(function () { + if (!String.prototype.capitalize) { + String.prototype.capitalize = function () { + return this.slice(0, 1).toUpperCase() + this.slice(1); + }; + } +})(); + +(function ($) { + $.fn.realWidth = function (type, margin, $el) { + var width = null, $div = null, method = null; + + type = type === 'inner' || type === 'outer' ? type : ''; + method = type === '' ? 'width' : type + 'Width'; + margin = margin === true ? true : false; + $div = $(this).clone().css({position: 'absolute', left: -10000}).appendTo($el || 'body'); + width = margin ? $div[method](margin) : $div[method](); + + $div.remove(); + + return width; + }; + + $.fn.realHeight = function (type, margin, $el) { + var height = null, $div = null, method = null; + + type = type === 'inner' || type === 'outer' ? type : ''; + method = type === '' ? 'height' : type + 'Height'; + margin = margin === true ? true : false; + $div = $(this).clone().css({position: 'absolute', left: -10000}).appendTo($el || 'body'); + height = margin ? $div[method](margin) : $div[method](); + + $div.remove(); + + return height; + }; + + $.fn.bindMobileEvents = function () { + $(this).on('touchstart touchmove touchend touchcancel', function () { + var touches = (event.changedTouches || event.originalEvent.targetTouches), + first = touches[0], + type = ''; + + switch (event.type) { + case 'touchstart': + type = 'mousedown'; + break; + case 'touchmove': + type = 'mousemove'; + event.preventDefault(); + break; + case 'touchend': + type = 'mouseup'; + break; + default: + return; + } + + var simulatedEvent = document.createEvent('MouseEvent'); + + simulatedEvent.initMouseEvent( + type, true, true, window, 1, + first.screenX, first.screenY, first.clientX, first.clientY, + false, false, false, false, 0/*left*/, null + ); + + first.target.dispatchEvent(simulatedEvent); + }); + }; +})(jQuery);
\ No newline at end of file |