/** * @file jGraduate 0.4 * * jQuery Plugin for a gradient picker * * @module jGraduate * @copyright 2010 Jeff Schiller {@link http://blog.codedread.com/}, 2010 Alexis Deveria {@link http://a.deveria.com/} * * @license Apache-2.0 * @example * // The Paint object is described below. * $.jGraduate.Paint(); // constructs a 'none' color * @example $.jGraduate.Paint({copy: o}); // creates a copy of the paint o * @example $.jGraduate.Paint({hex: '#rrggbb'}); // creates a solid color paint with hex = "#rrggbb" * @example $.jGraduate.Paint({linearGradient: o, a: 50}); // creates a linear gradient paint with opacity=0.5 * @example $.jGraduate.Paint({radialGradient: o, a: 7}); // creates a radial gradient paint with opacity=0.07 * @example $.jGraduate.Paint({hex: '#rrggbb', linearGradient: o}); // throws an exception? */ /* eslint-disable jsdoc/require-property */ /** * The jQuery namespace. * @external jQuery */ /** * The jQuery plugin namespace. * @namespace {PlainObject} fn * @memberof external:jQuery * @see {@link http://learn.jquery.com/plugins/|jQuery Plugins} */ /* eslint-enable jsdoc/require-property */ const ns = { svg: 'http://www.w3.org/2000/svg', xlink: 'http://www.w3.org/1999/xlink' }; if (!window.console) { window.console = { log (str) { /* */ }, dir (str) { /* */ } }; } /** * Adds {@link external:jQuery.jGraduate.Paint}, * {@link external:jQuery.fn.jGraduateDefaults}, * {@link external:jQuery.fn.jGraduate}. * @function module:jGraduate.jGraduate * @param {external:jQuery} $ The jQuery instance to wrap * @returns {external:jQuery} */ export default function jQueryPluginJGraduate ($) { /** * @typedef {PlainObject} module:jGraduate.jGraduatePaintOptions * @property {Float} [alpha] * @property {module:jGraduate~Paint} [copy] Copy paint object * @property {SVGLinearGradientElement} [linearGradient] * @property {SVGRadialGradientElement} [radialGradient] * @property {string} [solidColor] */ /** * @memberof module:jGraduate~ */ class Paint { /** * @param {module:jGraduate.jGraduatePaintOptions} [opt] */ constructor (opt) { const options = opt || {}; this.alpha = isNaN(options.alpha) ? 100 : options.alpha; // copy paint object if (options.copy) { /** * @name module:jGraduate~Paint#type * @type {"none"|"solidColor"|"linearGradient"|"radialGradient"} */ this.type = options.copy.type; /** * Represents opacity (0-100). * @name module:jGraduate~Paint#alpha * @type {Float} */ this.alpha = options.copy.alpha; /** * Represents #RRGGBB hex of color. * @name module:jGraduate~Paint#solidColor * @type {string} */ this.solidColor = null; /** * @name module:jGraduate~Paint#linearGradient * @type {SVGLinearGradientElement} */ this.linearGradient = null; /** * @name module:jGraduate~Paint#radialGradient * @type {SVGRadialGradientElement} */ this.radialGradient = null; switch (this.type) { case 'none': break; case 'solidColor': this.solidColor = options.copy.solidColor; break; case 'linearGradient': this.linearGradient = options.copy.linearGradient.cloneNode(true); break; case 'radialGradient': this.radialGradient = options.copy.radialGradient.cloneNode(true); break; } // create linear gradient paint } else if (options.linearGradient) { this.type = 'linearGradient'; this.solidColor = null; this.radialGradient = null; this.linearGradient = options.linearGradient.cloneNode(true); // create linear gradient paint } else if (options.radialGradient) { this.type = 'radialGradient'; this.solidColor = null; this.linearGradient = null; this.radialGradient = options.radialGradient.cloneNode(true); // create solid color paint } else if (options.solidColor) { this.type = 'solidColor'; this.solidColor = options.solidColor; // create empty paint } else { this.type = 'none'; this.solidColor = null; this.linearGradient = null; this.radialGradient = null; } } } /* eslint-disable jsdoc/require-property */ /** * @namespace {PlainObject} jGraduate * @memberof external:jQuery */ $.jGraduate = /** @lends external:jQuery.jGraduate */ { /* eslint-enable jsdoc/require-property */ /** * @class external:jQuery.jGraduate.Paint * @see module:jGraduate~Paint */ Paint }; // JSDoc doesn't show this as belonging to our `module:jGraduate.Options` type, // so we use `@see` /** * @namespace {module:jGraduate.Options} jGraduateDefaults * @memberof external:jQuery.fn */ $.fn.jGraduateDefaults = /** @lends external:jQuery.fn.jGraduateDefaults */ { /** * Creates an object with a 'none' color. * @type {external:jQuery.jGraduate.Paint} * @see module:jGraduate.Options */ paint: new $.jGraduate.Paint(), /** * @namespace */ window: { /** * @type {string} * @see module:jGraduate.Options */ pickerTitle: 'Drag markers to pick a paint' }, /** * @namespace */ images: { /** * @type {string} * @see module:jGraduate.Options */ clientPath: 'images/' }, /** * @type {string} * @see module:jGraduate.Options */ newstop: 'inverse' // same, inverse, black, white }; const isGecko = navigator.userAgent.includes('Gecko/'); /** * @typedef {PlainObject} module:jGraduate.Attrs */ /** * @param {SVGElement} elem * @param {module:jGraduate.Attrs} attrs * @returns {void} */ function setAttrs (elem, attrs) { if (isGecko) { Object.entries(attrs).forEach(([aname, val]) => { elem.setAttribute(aname, val); }); } else { Object.entries(attrs).forEach(([aname, val]) => { const prop = elem[aname]; if (prop && prop.constructor === 'SVGLength') { prop.baseVal.value = val; } else { elem.setAttribute(aname, val); } }); } } /** * @param {string} name * @param {module:jGraduate.Attrs} attrs * @param {Element} newparent * @returns {SVGElement} */ function mkElem (name, attrs, newparent) { const elem = document.createElementNS(ns.svg, name); setAttrs(elem, attrs); if (newparent) { newparent.append(elem); } return elem; } /** * @typedef {PlainObject} module:jGraduate.ColorOpac Object may have one or both values * @property {string} [color] #Hex color * @property {Float} [opac] 0-1 */ /** * @typedef {PlainObject} module:jGraduate.Options * @property {module:jGraduate~Paint} [paint] A Paint object object describing the paint to display initially; defaults to a new instance without options (defaults to opaque white) * @property {external:Window} [window] * @property {string} [window.pickerTitle='Drag markers to pick a paint'] * @property {PlainObject} [images] * @property {string} [images.clientPath='images/'] * @property {"same"|"inverse"|"black"|"white"|module:jGraduate.ColorOpac} [newstop="inverse"] */ /** * @callback external:jQuery.fn.jGraduate.OkCallback * @param {external:jQuery.jGraduate.Paint} paint * @returns {void} */ /** * @callback external:jQuery.fn.jGraduate.CancelCallback * @returns {void} */ /** * @function external:jQuery.fn.jGraduate * @param {module:jGraduate.Options} [options] * @param {external:jQuery.fn.jGraduate.OkCallback} [okCallback] Called with a Paint object when Ok is pressed * @param {external:jQuery.fn.jGraduate.CancelCallback} [cancelCallback] Called with no arguments when Cancel is pressed * @returns {external:jQuery} */ $.fn.jGraduate = function (options, okCallback, cancelCallback) { return this.each(function () { const $this = $(this), $settings = $.extend(true, {}, $.fn.jGraduateDefaults, options || {}), id = $this.attr('id'), idref = '#' + $this.attr('id') + ' '; if (!idref) { /* await */ $.alert('Container element must have an id attribute to maintain unique id strings for sub-elements.'); return; } const okClicked = function () { switch ($this.paint.type) { case 'radialGradient': $this.paint.linearGradient = null; break; case 'linearGradient': $this.paint.radialGradient = null; break; case 'solidColor': $this.paint.radialGradient = $this.paint.linearGradient = null; break; } typeof $this.okCallback === 'function' && $this.okCallback($this.paint); $this.hide(); }; const cancelClicked = function () { typeof $this.cancelCallback === 'function' && $this.cancelCallback(); $this.hide(); }; $.extend( true, $this, // public properties, methods, and callbacks { // make a copy of the incoming paint paint: new $.jGraduate.Paint({copy: $settings.paint}), okCallback: typeof okCallback === 'function' ? okCallback : null, cancelCallback: typeof cancelCallback === 'function' ? cancelCallback : null } ); let // pos = $this.position(), color = null; const $win = $(window); if ($this.paint.type === 'none') { $this.paint = new $.jGraduate.Paint({solidColor: 'ffffff'}); } $this.addClass('jGraduate_Picker'); /* eslint-disable max-len */ $this.html( '' + '
' + '
' + '
' + '
' ); const colPicker = $(idref + '> .jGraduate_colPick'); const gradPicker = $(idref + '> .jGraduate_gradPick'); gradPicker.html( '
' + '

' + $settings.window.pickerTitle + '

' + '
' + '
' + '
' + '
' + '
' + '' + '
' + '' + '' + '' + '' + '
' + '
' + '
' + '' + '
' + '' + '' + '' + '' + '
' + '
' + '
' + '
' + '
' + '' + '
' + '' + '' + '' + '' + '
' + '
' + '
' + '' + '
' + '
' + '' + '' + '' + '' + '
' + '
' + '
' + '
' + '' + '
' + '' + '
' + '
' + '
' + '
' + '' + '
' + '' + '
' + '' + '
' + '
' + '' + '
' + '' + '
' + '' + '
' + '
' + '' + '
' + '' + '
' + '' + '
' + '
' + '' + '
' + '' + '
' + '' + '
' + '
' + '
' + '' + '' + '
' ); /* eslint-enable max-len */ // -------------- // Set up all the SVG elements (the gradient, stops and rectangle) const MAX = 256, MARGINX = 0, MARGINY = 0, // STOP_RADIUS = 15 / 2, SIZEX = MAX - 2 * MARGINX, SIZEY = MAX - 2 * MARGINY; const attrInput = {}; const SLIDERW = 145; $('.jGraduate_SliderBar').width(SLIDERW); const container = $('#' + id + '_jGraduate_GradContainer')[0]; const svg = mkElem('svg', { id: id + '_jgraduate_svg', width: MAX, height: MAX, xmlns: ns.svg }, container); // This wasn't working as designed // let curType; // curType = curType || $this.paint.type; // if we are sent a gradient, import it let curType = $this.paint.type; let grad = $this.paint[curType]; let curGradient = grad; const gradalpha = $this.paint.alpha; const isSolid = curType === 'solidColor'; // Make any missing gradients switch (curType) { case 'solidColor': // fall through case 'linearGradient': if (!isSolid) { curGradient.id = id + '_lg_jgraduate_grad'; grad = curGradient = svg.appendChild(curGradient); // .cloneNode(true)); } mkElem('radialGradient', { id: id + '_rg_jgraduate_grad' }, svg); if (curType === 'linearGradient') { break; } // fall through case 'radialGradient': if (!isSolid) { curGradient.id = id + '_rg_jgraduate_grad'; grad = curGradient = svg.appendChild(curGradient); // .cloneNode(true)); } mkElem('linearGradient', { id: id + '_lg_jgraduate_grad' }, svg); } let stopGroup; // eslint-disable-line prefer-const if (isSolid) { grad = curGradient = $('#' + id + '_lg_jgraduate_grad')[0]; color = $this.paint[curType]; mkStop(0, '#' + color, 1); const type = typeof $settings.newstop; if (type === 'string') { switch ($settings.newstop) { case 'same': mkStop(1, '#' + color, 1); break; case 'inverse': { // Invert current color for second stop let inverted = ''; for (let i = 0; i < 6; i += 2) { // const ch = color.substr(i, 2); let inv = (255 - Number.parseInt(color.substr(i, 2), 16)).toString(16); if (inv.length < 2) inv = 0 + inv; inverted += inv; } mkStop(1, '#' + inverted, 1); break; } case 'white': mkStop(1, '#ffffff', 1); break; case 'black': mkStop(1, '#000000', 1); break; } } else if (type === 'object') { const opac = ('opac' in $settings.newstop) ? $settings.newstop.opac : 1; mkStop(1, ($settings.newstop.color || '#' + color), opac); } } const x1 = Number.parseFloat(grad.getAttribute('x1') || 0.0), y1 = Number.parseFloat(grad.getAttribute('y1') || 0.0), x2 = Number.parseFloat(grad.getAttribute('x2') || 1.0), y2 = Number.parseFloat(grad.getAttribute('y2') || 0.0); const cx = Number.parseFloat(grad.getAttribute('cx') || 0.5), cy = Number.parseFloat(grad.getAttribute('cy') || 0.5), fx = Number.parseFloat(grad.getAttribute('fx') || cx), fy = Number.parseFloat(grad.getAttribute('fy') || cy); const previewRect = mkElem('rect', { id: id + '_jgraduate_rect', x: MARGINX, y: MARGINY, width: SIZEX, height: SIZEY, fill: 'url(#' + id + '_jgraduate_grad)', 'fill-opacity': gradalpha / 100 }, svg); // stop visuals created here const beginCoord = $('
').attr({ class: 'grad_coord jGraduate_lg_field', title: 'Begin Stop' }).text(1).css({ top: y1 * MAX, left: x1 * MAX }).data('coord', 'start').appendTo(container); const endCoord = beginCoord.clone().text(2).css({ top: y2 * MAX, left: x2 * MAX }).attr('title', 'End stop').data('coord', 'end').appendTo(container); const centerCoord = $('
').attr({ class: 'grad_coord jGraduate_rg_field', title: 'Center stop' }).text('C').css({ top: cy * MAX, left: cx * MAX }).data('coord', 'center').appendTo(container); const focusCoord = centerCoord.clone().text('F').css({ top: fy * MAX, left: fx * MAX, display: 'none' }).attr('title', 'Focus point').data('coord', 'focus').appendTo(container); focusCoord[0].id = id + '_jGraduate_focusCoord'; // const coords = $(idref + ' .grad_coord'); // $(container).hover(function () { // coords.animate({ // opacity: 1 // }, 500); // }, function () { // coords.animate({ // opacity: .2 // }, 500); // }); let showFocus; $.each(['x1', 'y1', 'x2', 'y2', 'cx', 'cy', 'fx', 'fy'], function (i, attr) { const isRadial = isNaN(attr[1]); let attrval = curGradient.getAttribute(attr); if (!attrval) { // Set defaults if (isRadial) { // For radial points attrval = '0.5'; } else { // Only x2 is 1 attrval = attr === 'x2' ? '1.0' : '0.0'; } } attrInput[attr] = $('#' + id + '_jGraduate_' + attr) .val(attrval) .change(function () { // TODO: Support values < 0 and > 1 (zoomable preview?) if (isNaN(Number.parseFloat(this.value)) || this.value < 0) { this.value = 0.0; } else if (this.value > 1) { this.value = 1.0; } if (!(attr[0] === 'f' && !showFocus)) { if ((isRadial && curType === 'radialGradient') || (!isRadial && curType === 'linearGradient')) { curGradient.setAttribute(attr, this.value); } } const $elem = isRadial ? attr[0] === 'c' ? centerCoord : focusCoord : attr[1] === '1' ? beginCoord : endCoord; const cssName = attr.includes('x') ? 'left' : 'top'; $elem.css(cssName, this.value * MAX); }).change(); }); /** * * @param {Float} n * @param {Float|string} colr * @param {Float} opac * @param {boolean} [sel] * @param {SVGStopElement} [stopElem] * @returns {SVGStopElement} */ function mkStop (n, colr, opac, sel, stopElem) { const stop = stopElem || mkElem('stop', { 'stop-color': colr, 'stop-opacity': opac, offset: n }, curGradient); if (stopElem) { colr = stopElem.getAttribute('stop-color'); opac = stopElem.getAttribute('stop-opacity'); n = stopElem.getAttribute('offset'); } else { curGradient.append(stop); } if (opac === null) opac = 1; const pickerD = 'M-6.2,0.9c3.6-4,6.7-4.3,6.7-12.4c-0.2,7.9,' + '3.1,8.8,6.5,12.4c3.5,3.8,2.9,9.6,0,12.3c-3.1,2.8-10.4,' + '2.7-13.2,0C-9.6,9.9-9.4,4.4-6.2,0.9z'; const pathbg = mkElem('path', { d: pickerD, fill: 'url(#jGraduate_trans)', transform: 'translate(' + (10 + n * MAX) + ', 26)' }, stopGroup); const path = mkElem('path', { d: pickerD, fill: colr, 'fill-opacity': opac, transform: 'translate(' + (10 + n * MAX) + ', 26)', stroke: '#000', 'stroke-width': 1.5 }, stopGroup); $(path).mousedown(function (e) { selectStop(this); drag = curStop; $win.mousemove(dragColor).mouseup(remDrags); stopOffset = stopMakerDiv.offset(); e.preventDefault(); return false; }).data('stop', stop).data('bg', pathbg).dblclick(function () { $('div.jGraduate_LightBox').show(); const colorhandle = this; let stopOpacity = Number(stop.getAttribute('stop-opacity')) || 1; let stopColor = stop.getAttribute('stop-color') || 1; let thisAlpha = (Number.parseFloat(stopOpacity) * 255).toString(16); while (thisAlpha.length < 2) { thisAlpha = '0' + thisAlpha; } colr = stopColor.substr(1) + thisAlpha; $('#' + id + '_jGraduate_stopPicker').css({left: 100, bottom: 15}).jPicker({ window: {title: 'Pick the start color and opacity for the gradient'}, images: {clientPath: $settings.images.clientPath}, color: {active: colr, alphaSupport: true} }, function (clr, arg2) { stopColor = clr.val('hex') ? ('#' + clr.val('hex')) : 'none'; stopOpacity = clr.val('a') !== null ? clr.val('a') / 256 : 1; colorhandle.setAttribute('fill', stopColor); colorhandle.setAttribute('fill-opacity', stopOpacity); stop.setAttribute('stop-color', stopColor); stop.setAttribute('stop-opacity', stopOpacity); $('div.jGraduate_LightBox').hide(); $('#' + id + '_jGraduate_stopPicker').hide(); }, null, function () { $('div.jGraduate_LightBox').hide(); $('#' + id + '_jGraduate_stopPicker').hide(); }); }); $(curGradient).find('stop').each(function () { const curS = $(this); if (Number(this.getAttribute('offset')) > n) { if (!colr) { const newcolor = this.getAttribute('stop-color'); const newopac = this.getAttribute('stop-opacity'); stop.setAttribute('stop-color', newcolor); path.setAttribute('fill', newcolor); stop.setAttribute('stop-opacity', newopac === null ? 1 : newopac); path.setAttribute('fill-opacity', newopac === null ? 1 : newopac); } curS.before(stop); return false; } return true; }); if (sel) selectStop(path); return stop; } /** * * @returns {void} */ function remStop () { delStop.setAttribute('display', 'none'); const path = $(curStop); const stop = path.data('stop'); const bg = path.data('bg'); $([curStop, stop, bg]).remove(); } const stopMakerDiv = $('#' + id + '_jGraduate_StopSlider'); let stops, curStop, drag; const delStop = mkElem('path', { d: 'm9.75,-6l-19.5,19.5m0,-19.5l19.5,19.5', fill: 'none', stroke: '#D00', 'stroke-width': 5, display: 'none' }, undefined); // stopMakerSVG); /** * @param {Element} item * @returns {void} */ function selectStop (item) { if (curStop) curStop.setAttribute('stroke', '#000'); item.setAttribute('stroke', 'blue'); curStop = item; // stops = $('stop'); // opac_select.val(curStop.attr('fill-opacity') || 1); // root.append(delStop); } let stopOffset; /** * * @returns {void} */ function remDrags () { $win.unbind('mousemove', dragColor); if (delStop.getAttribute('display') !== 'none') { remStop(); } drag = null; } let scaleX = 1, scaleY = 1, angle = 0; let cX = cx; let cY = cy; /** * * @returns {void} */ function xform () { const rot = angle ? 'rotate(' + angle + ',' + cX + ',' + cY + ') ' : ''; if (scaleX === 1 && scaleY === 1) { curGradient.removeAttribute('gradientTransform'); // $('#ang').addClass('dis'); } else { const x = -cX * (scaleX - 1); const y = -cY * (scaleY - 1); curGradient.setAttribute( 'gradientTransform', rot + 'translate(' + x + ',' + y + ') scale(' + scaleX + ',' + scaleY + ')' ); // $('#ang').removeClass('dis'); } } /** * @param {Event} evt * @returns {void} */ function dragColor (evt) { let x = evt.pageX - stopOffset.left; const y = evt.pageY - stopOffset.top; x = x < 10 ? 10 : x > MAX + 10 ? MAX + 10 : x; const xfStr = 'translate(' + x + ', 26)'; if (y < -60 || y > 130) { delStop.setAttribute('display', 'block'); delStop.setAttribute('transform', xfStr); } else { delStop.setAttribute('display', 'none'); } drag.setAttribute('transform', xfStr); $.data(drag, 'bg').setAttribute('transform', xfStr); const stop = $.data(drag, 'stop'); const sX = (x - 10) / MAX; stop.setAttribute('offset', sX); let last = 0; $(curGradient).find('stop').each(function (i) { const cur = this.getAttribute('offset'); const t = $(this); if (cur < last) { t.prev().before(t); stops = $(curGradient).find('stop'); } last = cur; }); } const stopMakerSVG = mkElem('svg', { width: '100%', height: 45 }, stopMakerDiv[0]); const transPattern = mkElem('pattern', { width: 16, height: 16, patternUnits: 'userSpaceOnUse', id: 'jGraduate_trans' }, stopMakerSVG); const transImg = mkElem('image', { width: 16, height: 16 }, transPattern); const bgImage = $settings.images.clientPath + 'map-opacity.png'; transImg.setAttributeNS(ns.xlink, 'xlink:href', bgImage); $(stopMakerSVG).click(function (evt) { stopOffset = stopMakerDiv.offset(); const {target} = evt; if (target.tagName === 'path') return; let x = evt.pageX - stopOffset.left - 8; x = x < 10 ? 10 : x > MAX + 10 ? MAX + 10 : x; mkStop(x / MAX, 0, 0, true); evt.stopPropagation(); }); $(stopMakerSVG).mouseover(function () { stopMakerSVG.append(delStop); }); stopGroup = mkElem('g', {}, stopMakerSVG); mkElem('line', { x1: 10, y1: 15, x2: MAX + 10, y2: 15, 'stroke-width': 2, stroke: '#000' }, stopMakerSVG); const spreadMethodOpt = gradPicker.find('.jGraduate_spreadMethod').change(function () { curGradient.setAttribute('spreadMethod', $(this).val()); }); // handle dragging the stop around the swatch let draggingCoord = null; const onCoordDrag = function (evt) { let x = evt.pageX - offset.left; let y = evt.pageY - offset.top; // clamp stop to the swatch x = x < 0 ? 0 : x > MAX ? MAX : x; y = y < 0 ? 0 : y > MAX ? MAX : y; draggingCoord.css('left', x).css('top', y); // calculate stop offset const fracx = x / SIZEX; const fracy = y / SIZEY; const type = draggingCoord.data('coord'); const grd = curGradient; switch (type) { case 'start': attrInput.x1.val(fracx); attrInput.y1.val(fracy); grd.setAttribute('x1', fracx); grd.setAttribute('y1', fracy); break; case 'end': attrInput.x2.val(fracx); attrInput.y2.val(fracy); grd.setAttribute('x2', fracx); grd.setAttribute('y2', fracy); break; case 'center': attrInput.cx.val(fracx); attrInput.cy.val(fracy); grd.setAttribute('cx', fracx); grd.setAttribute('cy', fracy); cX = fracx; cY = fracy; xform(); break; case 'focus': attrInput.fx.val(fracx); attrInput.fy.val(fracy); grd.setAttribute('fx', fracx); grd.setAttribute('fy', fracy); xform(); } evt.preventDefault(); }; const onCoordUp = function () { draggingCoord = null; $win.unbind('mousemove', onCoordDrag).unbind('mouseup', onCoordUp); }; // Linear gradient // (function () { stops = curGradient.getElementsByTagNameNS(ns.svg, 'stop'); let numstops = stops.length; // if there are not at least two stops, then if (numstops < 2) { while (numstops < 2) { curGradient.append(document.createElementNS(ns.svg, 'stop')); ++numstops; } stops = curGradient.getElementsByTagNameNS(ns.svg, 'stop'); } for (let i = 0; i < numstops; i++) { mkStop(0, 0, 0, 0, stops[i]); } spreadMethodOpt.val(curGradient.getAttribute('spreadMethod') || 'pad'); let offset; // No match, so show focus point showFocus = false; previewRect.setAttribute('fill-opacity', gradalpha / 100); $('#' + id + ' div.grad_coord').mousedown(function (evt) { evt.preventDefault(); draggingCoord = $(this); // const sPos = draggingCoord.offset(); offset = draggingCoord.parent().offset(); $win.mousemove(onCoordDrag).mouseup(onCoordUp); }); // bind GUI elements $('#' + id + '_jGraduate_Ok').bind('click', function () { $this.paint.type = curType; $this.paint[curType] = curGradient.cloneNode(true); $this.paint.solidColor = null; okClicked(); }); $('#' + id + '_jGraduate_Cancel').bind('click', function (paint) { cancelClicked(); }); if (curType === 'radialGradient') { if (showFocus) { focusCoord.show(); } else { focusCoord.hide(); attrInput.fx.val(''); attrInput.fy.val(''); } } $('#' + id + '_jGraduate_match_ctr')[0].checked = !showFocus; let lastfx, lastfy; $('#' + id + '_jGraduate_match_ctr').change(function () { showFocus = !this.checked; focusCoord.toggle(showFocus); attrInput.fx.val(''); attrInput.fy.val(''); const grd = curGradient; if (!showFocus) { lastfx = grd.getAttribute('fx'); lastfy = grd.getAttribute('fy'); grd.removeAttribute('fx'); grd.removeAttribute('fy'); } else { const fX = lastfx || 0.5; const fY = lastfy || 0.5; grd.setAttribute('fx', fX); grd.setAttribute('fy', fY); attrInput.fx.val(fX); attrInput.fy.val(fY); } }); stops = curGradient.getElementsByTagNameNS(ns.svg, 'stop'); numstops = stops.length; // if there are not at least two stops, then if (numstops < 2) { while (numstops < 2) { curGradient.append(document.createElementNS(ns.svg, 'stop')); ++numstops; } stops = curGradient.getElementsByTagNameNS(ns.svg, 'stop'); } let slider; const setSlider = function (e) { const {offset: {left}} = slider; const div = slider.parent; let x = (e.pageX - left - Number.parseInt(div.css('border-left-width'))); if (x > SLIDERW) x = SLIDERW; if (x <= 0) x = 0; const posx = x - 5; x /= SLIDERW; switch (slider.type) { case 'radius': x = (x * 2) ** 2.5; if (x > 0.98 && x < 1.02) x = 1; if (x <= 0.01) x = 0.01; curGradient.setAttribute('r', x); break; case 'opacity': $this.paint.alpha = Number.parseInt(x * 100); previewRect.setAttribute('fill-opacity', x); break; case 'ellip': scaleX = 1; scaleY = 1; if (x < 0.5) { x /= 0.5; // 0.001 scaleX = x <= 0 ? 0.01 : x; } else if (x > 0.5) { x /= 0.5; // 2 x = 2 - x; scaleY = x <= 0 ? 0.01 : x; } xform(); x -= 1; if (scaleY === x + 1) { x = Math.abs(x); } break; case 'angle': x -= 0.5; angle = x *= 180; xform(); x /= 100; break; } slider.elem.css({'margin-left': posx}); x = Math.round(x * 100); slider.input.val(x); }; let ellipVal = 0, angleVal = 0; if (curType === 'radialGradient') { const tlist = curGradient.gradientTransform.baseVal; if (tlist.numberOfItems === 2) { const t = tlist.getItem(0); const s = tlist.getItem(1); if (t.type === 2 && s.type === 3) { const m = s.matrix; if (m.a !== 1) { ellipVal = Math.round(-(1 - m.a) * 100); } else if (m.d !== 1) { ellipVal = Math.round((1 - m.d) * 100); } } } else if (tlist.numberOfItems === 3) { // Assume [R][T][S] const r = tlist.getItem(0); const t = tlist.getItem(1); const s = tlist.getItem(2); if (r.type === 4 && t.type === 2 && s.type === 3 ) { angleVal = Math.round(r.angle); const m = s.matrix; if (m.a !== 1) { ellipVal = Math.round(-(1 - m.a) * 100); } else if (m.d !== 1) { ellipVal = Math.round((1 - m.d) * 100); } } } } const sliders = { radius: { handle: '#' + id + '_jGraduate_RadiusArrows', input: '#' + id + '_jGraduate_RadiusInput', val: (curGradient.getAttribute('r') || 0.5) * 100 }, opacity: { handle: '#' + id + '_jGraduate_OpacArrows', input: '#' + id + '_jGraduate_OpacInput', val: $this.paint.alpha || 100 }, ellip: { handle: '#' + id + '_jGraduate_EllipArrows', input: '#' + id + '_jGraduate_EllipInput', val: ellipVal }, angle: { handle: '#' + id + '_jGraduate_AngleArrows', input: '#' + id + '_jGraduate_AngleInput', val: angleVal } }; $.each(sliders, function (type, data) { const handle = $(data.handle); handle.mousedown(function (evt) { const parent = handle.parent(); slider = { type, elem: handle, input: $(data.input), parent, offset: parent.offset() }; $win.mousemove(dragSlider).mouseup(stopSlider); evt.preventDefault(); }); $(data.input).val(data.val).change(function () { const isRad = curType === 'radialGradient'; let val = Number(this.value); let xpos = 0; switch (type) { case 'radius': if (isRad) curGradient.setAttribute('r', val / 100); xpos = (((val / 100) ** (1 / 2.5)) / 2) * SLIDERW; break; case 'opacity': $this.paint.alpha = val; previewRect.setAttribute('fill-opacity', val / 100); xpos = val * (SLIDERW / 100); break; case 'ellip': scaleX = scaleY = 1; if (val === 0) { xpos = SLIDERW * 0.5; break; } if (val > 99.5) val = 99.5; if (val > 0) { scaleY = 1 - (val / 100); } else { scaleX = -(val / 100) - 1; } xpos = SLIDERW * ((val + 100) / 2) / 100; if (isRad) xform(); break; case 'angle': angle = val; xpos = angle / 180; xpos += 0.5; xpos *= SLIDERW; if (isRad) xform(); } if (xpos > SLIDERW) { xpos = SLIDERW; } else if (xpos < 0) { xpos = 0; } handle.css({'margin-left': xpos - 5}); }).change(); }); const dragSlider = function (evt) { setSlider(evt); evt.preventDefault(); }; const stopSlider = function (evt) { $win.unbind('mousemove', dragSlider).unbind('mouseup', stopSlider); slider = null; }; // -------------- let thisAlpha = ($this.paint.alpha * 255 / 100).toString(16); while (thisAlpha.length < 2) { thisAlpha = '0' + thisAlpha; } thisAlpha = thisAlpha.split('.')[0]; color = $this.paint.solidColor === 'none' ? '' : $this.paint.solidColor + thisAlpha; if (!isSolid) { color = stops[0].getAttribute('stop-color'); } // This should be done somewhere else, probably $.extend($.fn.jPicker.defaults.window, { alphaSupport: true, effects: {type: 'show', speed: 0} }); colPicker.jPicker( { window: {title: $settings.window.pickerTitle}, images: {clientPath: $settings.images.clientPath}, color: {active: color, alphaSupport: true} }, function (clr) { $this.paint.type = 'solidColor'; $this.paint.alpha = clr.val('ahex') ? Math.round((clr.val('a') / 255) * 100) : 100; $this.paint.solidColor = clr.val('hex') ? clr.val('hex') : 'none'; $this.paint.radialGradient = null; okClicked(); }, null, function () { cancelClicked(); } ); const tabs = $(idref + ' .jGraduate_tabs li'); tabs.click(function () { tabs.removeClass('jGraduate_tab_current'); $(this).addClass('jGraduate_tab_current'); $(idref + ' > div').hide(); const type = $(this).attr('data-type'); /* const container = */ $(idref + ' .jGraduate_gradPick').show(); if (type === 'rg' || type === 'lg') { // Show/hide appropriate fields $('.jGraduate_' + type + '_field').show(); $('.jGraduate_' + (type === 'lg' ? 'rg' : 'lg') + '_field').hide(); $('#' + id + '_jgraduate_rect')[0].setAttribute('fill', 'url(#' + id + '_' + type + '_jgraduate_grad)'); // Copy stops curType = type === 'lg' ? 'linearGradient' : 'radialGradient'; $('#' + id + '_jGraduate_OpacInput').val($this.paint.alpha).change(); const newGrad = $('#' + id + '_' + type + '_jgraduate_grad')[0]; if (curGradient !== newGrad) { const curStops = $(curGradient).find('stop'); $(newGrad).empty().append(curStops); curGradient = newGrad; const sm = spreadMethodOpt.val(); curGradient.setAttribute('spreadMethod', sm); } showFocus = type === 'rg' && curGradient.getAttribute('fx') !== null && !(cx === fx && cy === fy); $('#' + id + '_jGraduate_focusCoord').toggle(showFocus); if (showFocus) { $('#' + id + '_jGraduate_match_ctr')[0].checked = false; } } else { $(idref + ' .jGraduate_gradPick').hide(); $(idref + ' .jGraduate_colPick').show(); } }); $(idref + ' > div').hide(); tabs.removeClass('jGraduate_tab_current'); let tab; switch ($this.paint.type) { case 'linearGradient': tab = $(idref + ' .jGraduate_tab_lingrad'); break; case 'radialGradient': tab = $(idref + ' .jGraduate_tab_radgrad'); break; default: tab = $(idref + ' .jGraduate_tab_color'); break; } $this.show(); // jPicker will try to show after a 0ms timeout, so need to fire this after that setTimeout(() => { tab.addClass('jGraduate_tab_current').click(); }, 10); }); }; return $; }