(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 = $('
'); 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 = $('
').append($content.children()); var $bg = $('
'), $modal = $('
'), $holder = $('
'), $close = $('
X
'); 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 = $('
'); this.$menuHolder = $('
'); 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 = $('
'); // 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 = $('
'), $iconImg = $('
'), 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 = $('
') .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 = $('
'), $select = $('
'), 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('
') .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 = $('
').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);