diff --git a/src/extensions/default/InlineTimingFunctionEditor/TimingFunctionEditor.js b/src/extensions/default/InlineTimingFunctionEditor/BezierCurveEditor.js similarity index 94% rename from src/extensions/default/InlineTimingFunctionEditor/TimingFunctionEditor.js rename to src/extensions/default/InlineTimingFunctionEditor/BezierCurveEditor.js index a08b58d309a..82575fef136 100644 --- a/src/extensions/default/InlineTimingFunctionEditor/TimingFunctionEditor.js +++ b/src/extensions/default/InlineTimingFunctionEditor/BezierCurveEditor.js @@ -29,7 +29,6 @@ define(function (require, exports, module) { var EditorManager = brackets.getModule("editor/EditorManager"), KeyEvent = brackets.getModule("utils/KeyEvent"), - StringUtils = brackets.getModule("utils/StringUtils"), Strings = brackets.getModule("strings"); var TimingFunctionUtils = require("TimingFunctionUtils"), @@ -493,6 +492,10 @@ define(function (require, exports, module) { bezierEditor._commitTimingFunction(); bezierEditor._updateCanvas(); + return true; + + } else if (code === KeyEvent.DOM_VK_ESCAPE) { + return true; } return false; @@ -500,14 +503,14 @@ define(function (require, exports, module) { /** - * Constructor for TimingFunctionEditor Object. This control may be used standalone + * Constructor for BezierCurveEditor Object. This control may be used standalone * or within an InlineTimingFunctionEditor inline widget. * * @param {!jQuery} $parent DOM node into which to append the root of the bezier curve editor UI * @param {!RegExpMatch} bezierCurve RegExp match object of initially selected bezierCurve * @param {!function(string)} callback Called whenever selected bezierCurve changes */ - function TimingFunctionEditor($parent, bezierCurve, callback) { + function BezierCurveEditor($parent, bezierCurve, callback) { // Create the DOM structure, filling in localized strings via Mustache this.$element = $(Mustache.render(BezierCurveEditorTemplate, Strings)); $parent.append(this.$element); @@ -547,7 +550,7 @@ define(function (require, exports, module) { /** * Destructor called by InlineTimingFunctionEditor.onClosed() */ - TimingFunctionEditor.prototype.destroy = function () { + BezierCurveEditor.prototype.destroy = function () { this.P1.bezierEditor = this.P2.bezierEditor = this.curve.bezierEditor = null; @@ -567,40 +570,29 @@ define(function (require, exports, module) { }; - /** Returns the root DOM node of the TimingFunctionEditor UI */ - TimingFunctionEditor.prototype.getRootElement = function () { + /** Returns the root DOM node of the BezierCurveEditor UI */ + BezierCurveEditor.prototype.getRootElement = function () { return this.$element; }; /** * Default focus needs to go somewhere, so give it to P1 */ - TimingFunctionEditor.prototype.focus = function () { + BezierCurveEditor.prototype.focus = function () { this.P1.focus(); return true; }; /** - * Normalize the given bezierCurve string. - * - * @param {string} bezierCurve The bezierCurve to be corrected. - * @return {string} a normalized bezierCurve string. - */ - TimingFunctionEditor.prototype._normalizeTimingFunctionString = function (bezierCurve) { - return bezierCurve.toLowerCase(); - }; - - /** - * Sets _bezierCurve based on a string input, and updates the doc + * Generates cubic-bezier function based on coords, and updates the doc */ - TimingFunctionEditor.prototype._commitTimingFunction = function () { + BezierCurveEditor.prototype._commitTimingFunction = function () { var bezierCurveVal = "cubic-bezier(" + this._cubicBezierCoords[0] + ", " + this._cubicBezierCoords[1] + ", " + this._cubicBezierCoords[2] + ", " + this._cubicBezierCoords[3] + ")"; this._callback(bezierCurveVal); - this._bezierCurve = bezierCurveVal; }; /** @@ -610,7 +602,7 @@ define(function (require, exports, module) { * @param {RegExp.match} match Matches returned from cubicBezierMatch() * @return {Array.number[4]} */ - TimingFunctionEditor.prototype._getCubicBezierCoords = function (match) { + BezierCurveEditor.prototype._getCubicBezierCoords = function (match) { if (match[0].match(/^cubic-bezier/)) { // cubic-bezier() @@ -640,7 +632,7 @@ define(function (require, exports, module) { * * @return {left: number, top: number, width: number, height: number} */ - TimingFunctionEditor.prototype._getCurveBoundingBox = function () { + BezierCurveEditor.prototype._getCurveBoundingBox = function () { var $canvas = this.$element.find(".curve"), canvasOffset = $canvas.offset(); @@ -655,7 +647,7 @@ define(function (require, exports, module) { /** * Update after a change */ - TimingFunctionEditor.prototype._updateCanvas = function () { + BezierCurveEditor.prototype._updateCanvas = function () { // collect data, build model if (this._cubicBezierCoords) { this.bezierCanvas.bezier = window.bezier = new CubicBezier(this._cubicBezierCoords); @@ -680,11 +672,11 @@ define(function (require, exports, module) { * * @param {!RegExpMatch} bezierCurve RegExp match object of updated bezierCurve */ - TimingFunctionEditor.prototype.handleExternalUpdate = function (bezierCurve) { + BezierCurveEditor.prototype.handleExternalUpdate = function (bezierCurve) { this._cubicBezierCoords = this._getCubicBezierCoords(bezierCurve); this._updateCanvas(); }; - exports.TimingFunctionEditor = TimingFunctionEditor; + exports.BezierCurveEditor = BezierCurveEditor; }); diff --git a/src/extensions/default/InlineTimingFunctionEditor/BezierCurveEditorTemplate.html b/src/extensions/default/InlineTimingFunctionEditor/BezierCurveEditorTemplate.html index 0cf3d3c3377..8076b9ad29e 100644 --- a/src/extensions/default/InlineTimingFunctionEditor/BezierCurveEditorTemplate.html +++ b/src/extensions/default/InlineTimingFunctionEditor/BezierCurveEditorTemplate.html @@ -1,4 +1,4 @@ -
+
@@ -7,5 +7,8 @@
+
+

{{{BEZIER_EDITOR_INFO}}}

+
diff --git a/src/extensions/default/InlineTimingFunctionEditor/InlineTimingFunctionEditor.js b/src/extensions/default/InlineTimingFunctionEditor/InlineTimingFunctionEditor.js index f861bcb2efa..584b6efe72b 100644 --- a/src/extensions/default/InlineTimingFunctionEditor/InlineTimingFunctionEditor.js +++ b/src/extensions/default/InlineTimingFunctionEditor/InlineTimingFunctionEditor.js @@ -28,7 +28,8 @@ define(function (require, exports, module) { "use strict"; var InlineWidget = brackets.getModule("editor/InlineWidget").InlineWidget, - TimingFunctionEditor = require("TimingFunctionEditor").TimingFunctionEditor, + BezierCurveEditor = require("BezierCurveEditor").BezierCurveEditor, + StepEditor = require("StepEditor").StepEditor, TimingFunctionUtils = require("TimingFunctionUtils"); @@ -36,7 +37,7 @@ define(function (require, exports, module) { var lastOriginId = 1; /** - * Constructor for inline widget containing a TimingFunctionEditor control + * Constructor for inline widget containing a BezierCurveEditor control * * @param {!RegExpMatch} timingFunction RegExp match object of initially selected timingFunction * @param {!CodeMirror.Bookmark} startBookmark @@ -60,7 +61,7 @@ define(function (require, exports, module) { InlineTimingFunctionEditor.prototype.constructor = InlineTimingFunctionEditor; InlineTimingFunctionEditor.prototype.parentClass = InlineWidget.prototype; - /** @type {!TimingFunctionEditor} TimingFunctionEditor instance */ + /** @type {!BezierCurveEditor} BezierCurveEditor instance */ InlineTimingFunctionEditor.prototype.timingFunctionEditor = null; /** @type {!string} Current value of the timing function editor control */ @@ -120,7 +121,7 @@ define(function (require, exports, module) { // instead of two bookmarks to track the range. (In our current old version of // CodeMirror v2, markText() isn't robust enough for this case.) var line = this.hostEditor.document.getLine(start.line), - matches = TimingFunctionUtils.bezierCurveMatch(line.substr(start.ch), true); + matches = TimingFunctionUtils.timingFunctionMatch(line.substr(start.ch), true); // No longer have a match if (!matches) { @@ -152,7 +153,7 @@ define(function (require, exports, module) { * @param {!string} timingFunctionString */ InlineTimingFunctionEditor.prototype._handleTimingFunctionChange = function (timingFunctionString) { - var timingFunctionMatch = TimingFunctionUtils.bezierCurveMatch(timingFunctionString, true); + var timingFunctionMatch = TimingFunctionUtils.timingFunctionMatch(timingFunctionString, true); if (timingFunctionMatch !== this._timingFunction) { var range = this.getCurrentRange(); if (!range) { @@ -182,9 +183,14 @@ define(function (require, exports, module) { InlineTimingFunctionEditor.prototype.load = function (hostEditor) { InlineTimingFunctionEditor.prototype.parentClass.load.apply(this, arguments); - // Create timing function editor control - var swatchInfo = null; - this.timingFunctionEditor = new TimingFunctionEditor(this.$htmlContent, this._timingFunction, this._handleTimingFunctionChange, swatchInfo); + // Create appropriate timing function editor control + if (this._timingFunction.isBezier) { + this.timingFunctionEditor = new BezierCurveEditor(this.$htmlContent, this._timingFunction, this._handleTimingFunctionChange); + } else if (this._timingFunction.isStep) { + this.timingFunctionEditor = new StepEditor(this.$htmlContent, this._timingFunction, this._handleTimingFunctionChange); + } else { + window.console.log("InlineTimingFunctionEditor.load tried to load an unkown timing function type"); + } }; /** diff --git a/src/extensions/default/InlineTimingFunctionEditor/Localized.css b/src/extensions/default/InlineTimingFunctionEditor/Localized.css index 803ebdb7a80..8d2e093922a 100644 --- a/src/extensions/default/InlineTimingFunctionEditor/Localized.css +++ b/src/extensions/default/InlineTimingFunctionEditor/Localized.css @@ -1,15 +1,17 @@ -.timing-function-editor .coordinate-plane:after { +.bezier-curve-editor .coordinate-plane:after, +.step-editor .coordinate-plane:after { content: '{{INLINE_TIMING_EDITOR_TIME}}'; } -.timing-function-editor .coordinate-plane:hover:before { +.bezier-curve-editor .coordinate-plane:hover:before { content: '{{INLINE_TIMING_EDITOR_PROGRESSION}} (' attr(data-progression) '%)'; } -.timing-function-editor .coordinate-plane:before { +.bezier-curve-editor .coordinate-plane:before, +.step-editor .coordinate-plane:before { content: '{{INLINE_TIMING_EDITOR_PROGRESSION}}'; } -.timing-function-editor .coordinate-plane:hover:after { +.bezier-curve-editor .coordinate-plane:hover:after { content: '{{INLINE_TIMING_EDITOR_TIME}} (' attr(data-time) '%)'; } diff --git a/src/extensions/default/InlineTimingFunctionEditor/StepEditor.js b/src/extensions/default/InlineTimingFunctionEditor/StepEditor.js new file mode 100644 index 00000000000..94e52d2a31b --- /dev/null +++ b/src/extensions/default/InlineTimingFunctionEditor/StepEditor.js @@ -0,0 +1,430 @@ +/* + * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ + +/*jslint vars: true, plusplus: true, nomen: true, regexp: true, maxerr: 50 */ +/*global define, brackets, $, window, Mustache */ + +define(function (require, exports, module) { + "use strict"; + + var EditorManager = brackets.getModule("editor/EditorManager"), + KeyEvent = brackets.getModule("utils/KeyEvent"), + Strings = brackets.getModule("strings"); + + var TimingFunctionUtils = require("TimingFunctionUtils"), + InlineTimingFunctionEditor = require("InlineTimingFunctionEditor").InlineTimingFunctionEditor; + + /** Mustache template that forms the bare DOM structure of the UI */ + var StepEditorTemplate = require("text!StepEditorTemplate.html"); + + /** @const @type {number} */ + var STEP_LINE = 1, + DASH_LINE = 2, + HEIGHT_MAIN = 150, // height of main grid + WIDTH_MAIN = 150; // width of main grid + + var animationRequest = null; + + /** + * StepParameters object constructor + * + * @param {{ count: number, timing: string}} params Parameters passed to steps() + * either in string or array format. + */ + function StepParameters(params) { + if (!params) { + throw "No parameters were defined"; + } + + this.count = params.count; + this.timing = params.timing; + } + + /** + * StepCanvas object constructor + * + * @param {Element} canvas Inline editor element + * @param {StepParameters} stepParams Associated StepParameters object + * @param {number|Array.number} padding Element padding + */ + function StepCanvas(canvas, stepParams, padding) { + this.canvas = canvas; + this.stepParams = stepParams; + this.padding = this.getPadding(padding); + + // Convert to a cartesian coordinate system with axes from 0 to 1 + var ctx = this.canvas.getContext("2d"), + p = this.padding; + + ctx.scale(canvas.width * (1 - p[1] - p[3]), -canvas.height * (1 - p[0] - p[2])); + ctx.translate(p[3] / (1 - p[1] - p[3]), (-1 - p[0] / (1 - p[0] - p[2]))); + } + + StepCanvas.prototype = { + + drawBackground: function () { + this.ctx.beginPath(); + this.ctx.lineWidth = this.settings.borderWidth; + this.ctx.strokeStyle = this.settings.borderColor; + this.ctx.fillStyle = this.settings.bgColor; + this.ctx.moveTo(0, 0); + this.ctx.lineTo(0, 1); + this.ctx.lineTo(1, 1); + this.ctx.lineTo(1, 0); + this.ctx.lineTo(0, 0); + this.ctx.stroke(); + this.ctx.fill(); + this.ctx.closePath(); + }, + + drawPoint: function (x, y, isFilled) { + // Points are always step color + this.ctx.beginPath(); + this.ctx.lineWidth = this.settings.pointLineWidth; + this.ctx.strokeStyle = this.settings.stepColor; + this.ctx.arc(x, y, this.settings.pointRadius, 0, 2 * Math.PI, false); + this.ctx.stroke(); + if (isFilled) { + this.ctx.fillStyle = this.settings.stepColor; + this.ctx.fill(); + } + this.ctx.closePath(); + }, + + drawLine: function (x1, y1, x2, y2, type) { + this.ctx.beginPath(); + if (type === STEP_LINE) { + this.ctx.lineWidth = this.settings.stepLineWidth; + this.ctx.strokeStyle = this.settings.stepColor; + } else if (type === DASH_LINE) { + this.ctx.lineWidth = this.settings.dashLineWidth; + this.ctx.strokeStyle = this.settings.dashColor; + } + this.ctx.moveTo(x1, y1); + this.ctx.lineTo(x2, y2); + this.ctx.stroke(); + this.ctx.closePath(); + }, + + drawStartInterval: function (x1, y1, x2, y2) { + var pr = this.settings.pointRadius; + + // Draw empty start point + this.drawPoint(x1, y1, false); + + // Draw dashed line up to next step + this.drawLine(x1, y1 + pr, x1, y2, DASH_LINE); + + // Draw filled mid point + this.drawPoint(x1, y2, true); + + // Draw step line + this.drawLine(x1, y2, x2 - pr, y2, STEP_LINE); + }, + + drawEndInterval: function (x1, y1, x2, y2) { + var pr = this.settings.pointRadius; + + // Draw filled start point + this.drawPoint(x1, y1, true); + + // Draw step line + this.drawLine(x1, y1, x2 - pr, y1, STEP_LINE); + + // Draw empty mid point + this.drawPoint(x2, y1, false); + + // Draw dashed line up to next step + this.drawLine(x2, y1 + pr, x2, y2, DASH_LINE); + }, + + /** + * Paint canvas + * + * @param {Object} settings Paint settings + */ + plot: function (settings) { + var setting, i, j, last, interval, + sp = this.stepParams, + isStart = (sp.timing === "start"), + p = []; + + var defaultSettings = { + bgColor: "#fff", + borderColor: "#bbb", + stepColor: "#1461fc", + dashColor: "#b8b8b8", + borderWidth: 0.00667, + stepLineWidth: 0.02, + dashLineWidth: 0.008, + pointLineWidth: 0.008, + pointRadius: 0.015 + }; + + this.settings = settings || {}; + + for (setting in defaultSettings) { + if (defaultSettings.hasOwnProperty(setting)) { + if (!this.settings.hasOwnProperty(setting)) { + this.settings[setting] = defaultSettings[setting]; + } + } + } + + this.ctx = this.canvas.getContext("2d"); + + // Build points array. There's a starting point at 0,0 + // plus a point for each step + p[0] = { x: 0, y: 0 }; + for (i = 1; i <= sp.count; i++) { + interval = i / sp.count; + p[i] = { x: interval, y: interval }; + } + + // Start with a clean slate + this.ctx.clearRect(-0.5, -0.5, 2, 2); + this.drawBackground(); + + // Draw each interval + last = p.length - 1; + for (i = 0, j = 1; i < last; i++, j++) { + if (isStart) { + this.drawStartInterval(p[i].x, p[i].y, p[j].x, p[j].y); + } else { + this.drawEndInterval(p[i].x, p[i].y, p[j].x, p[j].y); + } + } + + // Each interval draws start and mid point for that interval, + // so we need to draw last point. It's always filled. + this.drawPoint(p[last].x, p[last].y, true); + }, + + /** + * Convert CSS padding shorthand to longhand + * + * @param {number|Array.number} padding Element padding + * @return {Array.number} + */ + getPadding: function (padding) { + var p = (typeof padding === "number") ? [padding] : padding; + + if (p.length === 1) { + p[1] = p[0]; + } + if (p.length === 2) { + p[2] = p[0]; + } + if (p.length === 3) { + p[3] = p[1]; + } + + return p; + } + }; + + // Event handlers + + /** + * Handle key down in element + * + * @param {Event} e Key down event + */ + function _canvasKeyDown(e) { + var code = e.keyCode, + self = e.target, + stepEditor = self.stepEditor; + + if (code >= KeyEvent.DOM_VK_LEFT && code <= KeyEvent.DOM_VK_DOWN) { + e.preventDefault(); + + // Arrow keys pressed + switch (code) { + case KeyEvent.DOM_VK_LEFT: + stepEditor.stepCanvas.stepParams.timing = "start"; + break; + case KeyEvent.DOM_VK_UP: + // No upper limit + stepEditor.stepCanvas.stepParams.count++; + break; + case KeyEvent.DOM_VK_RIGHT: + stepEditor.stepCanvas.stepParams.timing = "end"; + break; + case KeyEvent.DOM_VK_DOWN: + if (stepEditor.stepCanvas.stepParams.count > 1) { + stepEditor.stepCanvas.stepParams.count--; + } + break; + } + + // update step params + stepEditor._stepParams = stepEditor.stepCanvas.stepParams; + + stepEditor._commitTimingFunction(); + stepEditor._updateCanvas(); + return true; + + } else if (code === KeyEvent.DOM_VK_ESCAPE) { + return true; + } + + return false; + } + + + /** + * Constructor for StepEditor Object. This control may be used standalone + * or within an InlineTimingFunctionEditor inline widget. + * + * @param {!jQuery} $parent DOM node into which to append the root of the step editor UI + * @param {!RegExpMatch} stepMatch RegExp match object of initially selected step function + * @param {!function(string)} callback Called whenever selected step function changes + */ + function StepEditor($parent, stepMatch, callback) { + // Create the DOM structure, filling in localized strings via Mustache + this.$element = $(Mustache.render(StepEditorTemplate, Strings)); + $parent.append(this.$element); + + this._callback = callback; + + // current step function params + this._stepParams = this._getStepParams(stepMatch); + + this.canvas = this.$element.find(".steps")[0]; + + this.canvas.stepEditor = this; + + // Padding (3rd param)is scaled, so 0.1 translates to 15px + // Note that this is rendered inside canvas CSS "content" + // (i.e. this does not map to CSS padding) + this.stepCanvas = new StepCanvas(this.canvas, null, [0.1]); + + // redraw canvas + this._updateCanvas(); + + $(this.canvas).on("keydown", _canvasKeyDown); + } + + /** + * Destructor called by InlineTimingFunctionEditor.onClosed() + */ + StepEditor.prototype.destroy = function () { + this.canvas.stepEditor = null; + $(this.canvas).off("keydown", _canvasKeyDown); + }; + + + /** Returns the root DOM node of the StepEditor UI */ + StepEditor.prototype.getRootElement = function () { + return this.$element; + }; + + /** + * Default focus needs to go somewhere, so give it to canvas + */ + StepEditor.prototype.focus = function () { + this.canvas.focus(); + return true; + }; + + /** + * Generates step function based on parameters, and updates the doc + */ + StepEditor.prototype._commitTimingFunction = function () { + var stepFuncVal = "steps(" + + this._stepParams.count.toString() + ", " + + this._stepParams.timing + ")"; + this._callback(stepFuncVal); + }; + + /** + * Handle all matches returned from TimingFunctionUtils.stepMatch() and + * return array of coords + * + * @param {RegExp.match} match Matches returned from stepMatch() + * @return {{count: number, timing: string}} + */ + StepEditor.prototype._getStepParams = function (match) { + + if (match[0].match(/^steps/)) { + // steps() + return { + count: parseInt(match[1], 10), + timing: match[2] || "end" + }; + } else { + // handle special cases of steps functions + switch (match[0]) { + case "step-start": + return { count: 1, timing: "start" }; + case "step-end": + return { count: 1, timing: "end" }; + } + } + + window.console.log("step timing function: _getStepParams() passed invalid RegExp match array"); + return { count: 1, timing: "end" }; + }; + + /** + * Get element's bounding box + * + * @return {left: number, top: number, width: number, height: number} + */ + StepEditor.prototype._getCanvasBoundingBox = function () { + var $canvas = this.$element.find(".steps"), + canvasOffset = $canvas.offset(); + + return { + left: canvasOffset.left, + top: canvasOffset.top, + width: $canvas.width(), + height: $canvas.height() + }; + }; + + /** + * Update after a change + */ + StepEditor.prototype._updateCanvas = function () { + // collect data, build model + if (this._stepParams) { + this.stepCanvas.stepParams = window.stepParams = new StepParameters(this._stepParams); + + this.stepCanvas.plot(); + } + }; + + /** + * Handle external update + * + * @param {!RegExpMatch} stepMatch RegExp match object of updated step function + */ + StepEditor.prototype.handleExternalUpdate = function (stepMatch) { + this._stepParams = this._getStepParams(stepMatch); + this._updateCanvas(); + }; + + + exports.StepEditor = StepEditor; +}); diff --git a/src/extensions/default/InlineTimingFunctionEditor/StepEditorTemplate.html b/src/extensions/default/InlineTimingFunctionEditor/StepEditorTemplate.html new file mode 100644 index 00000000000..46ffced371b --- /dev/null +++ b/src/extensions/default/InlineTimingFunctionEditor/StepEditorTemplate.html @@ -0,0 +1,10 @@ +
+
+
+ +
+
+

{{{STEPS_EDITOR_INFO}}}

+
+
+
diff --git a/src/extensions/default/InlineTimingFunctionEditor/TimingFunctionUtils.js b/src/extensions/default/InlineTimingFunctionEditor/TimingFunctionUtils.js index 2b1679d96e0..aba71cfbbbe 100644 --- a/src/extensions/default/InlineTimingFunctionEditor/TimingFunctionUtils.js +++ b/src/extensions/default/InlineTimingFunctionEditor/TimingFunctionUtils.js @@ -31,10 +31,24 @@ define(function (require, exports, module) { "use strict"; /** - * Regular expression that matches CSS cubic-beziers functions (4 parameters). + * Regular expressions for matching timing functions * @const @type {RegExp} */ - var BEZIER_CURVE_REGEX = /cubic-bezier\(\s*(\S+)\s*,\s*(\S+)\s*,\s*(\S+)\s*,\s*(\S+)\s*\)/; + var BEZIER_CURVE_REGEX = /cubic-bezier\(\s*(\S+)\s*,\s*(\S+)\s*,\s*(\S+)\s*,\s*(\S+)\s*\)/, + EASE_STRICT_REGEX = /[: ,]ease(?:-in)?(?:-out)?[ ,;]/, + EASE_LAX_REGEX = /ease(?:-in)?(?:-out)?/, + LINEAR_STRICT_REGEX = /transition.*?[: ,]linear[ ,;]/, + LINEAR_LAX_REGEX = /linear/, + STEPS_REGEX = /steps\(\s*(\d+)\s*(?:,\s*(start|end)\s*)?\)/, + STEP_STRICT_REGEX = /[: ,](?:step-start|step-end)[ ,;]/, + STEP_LAX_REGEX = /step-start|step-end/; + + /** + * Type constants + * @const @type {number} + */ + var BEZIER = 1, + STEP = 2; /** * If string is a number, then convert it. @@ -58,13 +72,13 @@ define(function (require, exports, module) { } /** - * Validate cubic-bezier function parameters + * Validate cubic-bezier function parameters that are not already validated by regex: * * @param {RegExp.match} match RegExp Match object with cubic-bezier function parameters * in array positions 1-4. * @return {boolean} true if all parameters are valid, otherwise, false */ - function _validateParams(match) { + function _validateCubicBezierParams(match) { var x1 = _convertToNumber(match[1]), y1 = _convertToNumber(match[2]), x2 = _convertToNumber(match[3]), @@ -84,63 +98,161 @@ define(function (require, exports, module) { } /** - * Match a timing function value from a CSS Declaration or Value. + * Validate steps function parameters that are not already validated by regex: + * + * @param {RegExp.match} match RegExp Match object with steps function parameters + * in array position 1 (and optionally 2). + * @return {boolean} true if all parameters are valid, otherwise, false + */ + function _validateStepsParams(match) { + var count = _convertToNumber(match[1]); + + if (!count.isNumber || count.value <= 0) { + return false; + } + + return true; + } + + /** + * Tag this match with type and return it for chaining + * + * @param {!RegExp.match} match RegExp Match object with steps function parameters + * in array position 1 (and optionally 2). + * @param {number} type Either BEZIER or STEP + * @return {RegExp.match} Same object that was passed in. + */ + function _tagMatch(match, type) { + switch (type) { + case BEZIER: + match.isBezier = true; + break; + case STEP: + match.isStep = true; + break; + } + + return match; + } + + /** + * Match a bezier curve function value from a CSS Declaration or Value. * * Matches returned from this function must be handled in - * TimingFunctionEditor._getCubicBezierCoords(). + * BezierCurveEditor._getCubicBezierCoords(). * * @param {string} str Input string. * @param {!boolean} lax Parsing mode where: * lax=false Input is a Full or partial line containing CSS Declaration. * This is the more strict search used for initial detection. * lax=true Input is a previously parsed value. This is the less strict search - * used to convert previouslt parsed values to RegExp match format. + * used to convert previously parsed values to RegExp match format. * @return {!RegExpMatch} */ function bezierCurveMatch(str, lax) { - // First look for cubic-bezier(...). + // First look for cubic-bezier(x1,y1,x2,y2). var match = str.match(BEZIER_CURVE_REGEX); if (match) { - return _validateParams(match) ? match : null; + return _validateCubicBezierParams(match) ? _tagMatch(match, BEZIER) : null; } // Next look for the ease functions (which are special cases of cubic-bezier()) if (lax) { // For lax parsing, just look for the keywords - match = str.match(/ease(?:-in)?(?:-out)?/); + match = str.match(EASE_LAX_REGEX); if (match) { - return match; + return _tagMatch(match, BEZIER); } } else { // For strict parsing, start with a syntax verifying search - match = str.match(/[: ,]ease(?:-in)?(?:-out)?[ ,;]/); + match = str.match(EASE_STRICT_REGEX); if (match) { // return exact match to keyword that we need for later replacement - return str.match(/ease(?:-in)?(?:-out)?/); + return _tagMatch(str.match(EASE_LAX_REGEX), BEZIER); } } // Final case is linear. if (lax) { // For lax parsing, just look for the keyword - match = str.match(/linear/); + match = str.match(LINEAR_LAX_REGEX); if (match) { - return match; + return _tagMatch(match, BEZIER); } } else { // The linear keyword can occur in other values, so for strict parsing we // only detect when it's on same line as "transition" - match = str.match(/transition.*?[: ,]linear[ ,;]/); + match = str.match(LINEAR_STRICT_REGEX); if (match) { // return exact match to keyword that we need for later replacement - return str.match(/linear/); + return _tagMatch(str.match(LINEAR_LAX_REGEX), BEZIER); } } return null; } + /** + * Match a steps function value from a CSS Declaration or Value. + * + * Matches returned from this function must be handled in + * BezierCurveEditor._getCubicBezierCoords(). + * + * @param {string} str Input string. + * @param {!boolean} lax Parsing mode where: + * lax=false Input is a Full or partial line containing CSS Declaration. + * This is the more strict search used for initial detection. + * lax=true Input is a previously parsed value. This is the less strict search + * used to convert previously parsed values to RegExp match format. + * @return {!RegExpMatch} + */ + function stepsMatch(str, lax) { + // First look for steps(i,pos). + var match = str.match(STEPS_REGEX); + if (match) { + return _validateStepsParams(match) ? _tagMatch(match, STEP) : null; + } + + // Next look for the step functions (which are special cases of steps()) + if (lax) { + // For lax parsing, just look for the keywords + match = str.match(STEP_LAX_REGEX); + if (match) { + return _tagMatch(match, STEP); + } + } else { + // For strict parsing, start with a syntax verifying search + match = str.match(STEP_STRICT_REGEX); + if (match) { + // return exact match to keyword that we need for later replacement + return _tagMatch(str.match(STEP_LAX_REGEX), STEP); + } + } + + return null; + } + + /** + * Match a timing function value from a CSS Declaration or Value. + * + * Matches returned from this function must be handled in + * BezierCurveEditor._getCubicBezierCoords(). + * + * @param {string} str Input string. + * @param {!boolean} lax Parsing mode where: + * lax=false Input is a Full or partial line containing CSS Declaration. + * This is the more strict search used for initial detection. + * lax=true Input is a previously parsed value. This is the less strict search + * used to convert previously parsed values to RegExp match format. + * @return {!RegExpMatch} + */ + function timingFunctionMatch(str, lax) { + return bezierCurveMatch(str, lax) || stepsMatch(str, lax); + } + // Define public API + exports.timingFunctionMatch = timingFunctionMatch; exports.bezierCurveMatch = bezierCurveMatch; + exports.stepsMatch = stepsMatch; }); diff --git a/src/extensions/default/InlineTimingFunctionEditor/main.css b/src/extensions/default/InlineTimingFunctionEditor/main.css index dfa58abf820..1e4e7952cfc 100644 --- a/src/extensions/default/InlineTimingFunctionEditor/main.css +++ b/src/extensions/default/InlineTimingFunctionEditor/main.css @@ -1,22 +1,22 @@ /** - * All selectors descend from .timing-function-editor to constrain styles to this extension + * All BezierCurveEditor selectors descend from .bezier-curve-editor to constrain styles to this extension */ -.timing-function-editor * { +.bezier-curve-editor * { margin: 0; } -.timing-function-editor { +.bezier-curve-editor { position: relative; height: 300px; margin: 0 25px; } -.timing-function-editor:focus { +.bezier-curve-editor:focus { outline: none; } -.timing-function-editor .cubic-bezier { - background: url(grid.png) 0px -75px repeat; +.bezier-curve-editor .cubic-bezier { + background: url(grid.png) 0px -75px repeat-x; border-left: 1px solid #bbb; border-right: 1px solid #bbb; background-size: 15px 450px; @@ -28,7 +28,7 @@ z-index: 2; } -.timing-function-editor:after { +.bezier-curve-editor:after { content: ""; display: block; height: 300px; @@ -41,7 +41,7 @@ z-index: 1; } -.timing-function-editor .coordinate-plane { +.bezier-curve-editor .coordinate-plane { position: relative; left: 0; line-height: 0; @@ -52,8 +52,8 @@ box-sizing: content-box; } -.timing-function-editor .coordinate-plane:before, -.timing-function-editor .coordinate-plane:after { +.bezier-curve-editor .coordinate-plane:before, +.bezier-curve-editor .coordinate-plane:after { position: absolute; bottom: 0%; left: 0; @@ -68,7 +68,7 @@ line-height: 1; } -.timing-function-editor .coordinate-plane:before { +.bezier-curve-editor .coordinate-plane:before { border-bottom: 1px solid transparent; -moz-transform: rotate(-90deg); -ms-transform: rotate(-90deg); @@ -83,12 +83,12 @@ white-space: nowrap; } -.timing-function-editor .coordinate-plane:after { +.bezier-curve-editor .coordinate-plane:after { margin-bottom: -1.5em; white-space: nowrap; } -.timing-function-editor .control-point { +.bezier-curve-editor .control-point { background: -webkit-linear-gradient(270deg, rgb(249, 249, 249) 50%, rgb(237, 237, 237) 50%); position: absolute; z-index: 2; @@ -102,12 +102,12 @@ border-radius: 8px; } -.timing-function-editor .control-point:focus { +.bezier-curve-editor .control-point:focus { border: 1px solid #0940fd !important; box-shadow: 0 0 0 2px #6fb5f1; } -.timing-function-editor .P0, .timing-function-editor .P3 { +.bezier-curve-editor .P0, .bezier-curve-editor .P3 { background: #1461FC; border-radius: 0; border: none; @@ -116,22 +116,22 @@ z-index: 1; } -.timing-function-editor .P1, .timing-function-editor .P2 { +.bezier-curve-editor .P1, .bezier-curve-editor .P2 { cursor: pointer; z-index: 100; } -.timing-function-editor .P0 { +.bezier-curve-editor .P0 { left: 2px; top: 151px; } -.timing-function-editor .P3 { +.bezier-curve-editor .P3 { right: -7px; top: 1px; } -.timing-function-editor canvas.curve { +.bezier-curve-editor canvas.curve { position: absolute; z-index: 1; top: -75px; @@ -141,3 +141,148 @@ -webkit-user-select: none; user-select: none; } + +.bezier-curve-editor .info { + position: absolute; + z-index: 1; + top: 20px; + left: 170px; + width: 400px; + + font-family: SourceSansPro; + font-size: 11px; + line-height: 1.9 !important; +} + + +/** + * All StepEditor selectors descend from .step-editor to constrain styles to this extension + */ +.step-editor * { + margin: 0; +} + +.step-editor { + position: relative; + height: 190px; + margin: 0 25px; +} + +.step-editor:focus, .step-editor canvas.steps:focus { + outline: none; +} + +.step-editor .steps-func { + height: 179px; + width: 179px; + margin: 0 0 0 17px; + position: absolute; + top: 0; + z-index: 2; +} + +.step-editor:after { + content: ""; + display: block; + height: 179px; + width: 179px; + margin: 0 0 0 25px; + background-position: 2px 0; + position: absolute; + top: 0; + z-index: 1; +} + +.step-editor .coordinate-plane { + position: relative; + left: 0; + line-height: 0; + top: 0; + width: 179px; + height: 179px; + -moz-box-sizing: content-box; + box-sizing: content-box; +} + +.step-editor .coordinate-plane:before, +.step-editor .coordinate-plane:after { + position: absolute; + bottom: 0%; + left: 0; + width: 100%; + padding: .3em .5em; + + -moz-box-sizing: border-box; + box-sizing: border-box; + + color: #454545; + font-size: 75%; + line-height: 1; +} + +.step-editor .coordinate-plane:before { + border-bottom: 1px solid transparent; + -moz-transform: rotate(-90deg); + -ms-transform: rotate(-90deg); + -o-transform: rotate(-90deg); + -webkit-transform: rotate(-90deg); + transform: rotate(-90deg); + -moz-transform-origin: top left; + -ms-transform-origin: top left; + -o-transform-origin: top left; + -webkit-transform-origin: top left; + transform-origin: top left; + white-space: nowrap; +} + +.step-editor .coordinate-plane:after { + margin-left: 15px; + white-space: nowrap; +} + +.step-editor canvas.steps { + position: absolute; + z-index: 1; + top: 0; + left: 0; + + -moz-user-select: none; + -webkit-user-select: none; + user-select: none; +} + +.step-editor .info { + position: absolute; + z-index: 1; + top: 20px; + left: 180px; + width: 400px; + + font-family: SourceSansPro; + font-size: 11px; + line-height: 1.9 !important; +} + +.bezier-curve-editor kbd, +.step-editor kbd { + font-size: 15px; + font-weight: bold; + color: #454545; + background: rgba(255, 255, 255, 0.5); + border: 1px solid rgba(0, 0, 0, 0.1); + display: inline-block; + margin: 0 2px 2px 0; + padding: 0 3px 2px; + border-radius: 2px; + vertical-align: top; + line-height: 1; +} + +.bezier-curve-editor kbd.text, +.step-editor kbd.text { + font-size: 10px; + font-family: SourceSansPro; + font-weight: normal; + padding: 4px 4px; +} + diff --git a/src/extensions/default/InlineTimingFunctionEditor/main.js b/src/extensions/default/InlineTimingFunctionEditor/main.js index 616b9d89987..ab319e118aa 100644 --- a/src/extensions/default/InlineTimingFunctionEditor/main.js +++ b/src/extensions/default/InlineTimingFunctionEditor/main.js @@ -53,7 +53,7 @@ define(function (require, exports, module) { InlineTimingFunctionEditor = require("InlineTimingFunctionEditor").InlineTimingFunctionEditor, TimingFunctionUtils = require("TimingFunctionUtils"), - Localized = require("text!Localized.css"); + Localized = require("text!Localized.css"); // Functions @@ -80,11 +80,11 @@ define(function (require, exports, module) { // code runs several matches complicated patterns, multiple times, so // first do a quick, simple check to see make sure we may have a match - if (!cursorLine.match(/cubic-bezier|linear|ease/)) { + if (!cursorLine.match(/cubic-bezier|linear|ease|step/)) { return null; } - currentMatch = TimingFunctionUtils.bezierCurveMatch(cursorLine, false); + currentMatch = TimingFunctionUtils.timingFunctionMatch(cursorLine, false); if (!currentMatch) { return null; } @@ -93,7 +93,7 @@ define(function (require, exports, module) { var lineOffset = 0; while (pos.ch > (currentMatch.index + currentMatch[0].length + lineOffset)) { var restOfLine = cursorLine.substring(currentMatch.index + currentMatch[0].length + lineOffset), - newMatch = TimingFunctionUtils.bezierCurveMatch(restOfLine, false); + newMatch = TimingFunctionUtils.timingFunctionMatch(restOfLine, false); if (newMatch) { lineOffset += (currentMatch.index + currentMatch[0].length); diff --git a/src/extensions/default/InlineTimingFunctionEditor/unittest-files/unittests.css b/src/extensions/default/InlineTimingFunctionEditor/unittest-files/unittests.css index 7f58c3c9a33..9be902e2ad6 100644 --- a/src/extensions/default/InlineTimingFunctionEditor/unittest-files/unittests.css +++ b/src/extensions/default/InlineTimingFunctionEditor/unittest-files/unittests.css @@ -13,3 +13,11 @@ .bar { transition: width 1s cubic-bezier(.86, .11, .18, .86) 0s, height 500ms cubic-bezier(.27, .75, .78, .14) 100ms; } + +.baz { + transition-timing-function: steps(4, end); + transition-timing-function: steps(12, start); + transition-timing-function: steps(24); + transition-timing-function: step-start; + transition-timing-function: step-end; +} diff --git a/src/extensions/default/InlineTimingFunctionEditor/unittests.js b/src/extensions/default/InlineTimingFunctionEditor/unittests.js index 1d057b03e5f..bce756cf859 100644 --- a/src/extensions/default/InlineTimingFunctionEditor/unittests.js +++ b/src/extensions/default/InlineTimingFunctionEditor/unittests.js @@ -33,7 +33,8 @@ define(function (require, exports, module) { testContentCSS = require("text!unittest-files/unittests.css"), provider = require("main").inlineTimingFunctionEditorProvider, TimingFunctionUtils = require("TimingFunctionUtils"), - TimingFunctionEditor = require("TimingFunctionEditor").TimingFunctionEditor; + BezierCurveEditor = require("BezierCurveEditor").BezierCurveEditor, + StepEditor = require("StepEditor").StepEditor; describe("Inline Timing Function Editor", function () { @@ -72,140 +73,277 @@ define(function (require, exports, module) { }); } - describe("TimingFunctionUtils", function () { + describe("TimingFunctionUtils for bezier curve functions", function () { var match; // Valid cubic-bezier function cases it("should match bezier curve function in strict mode", function () { - match = TimingFunctionUtils.bezierCurveMatch("cubic-bezier(.1, .2, .3, .4)", false); + match = TimingFunctionUtils.timingFunctionMatch("cubic-bezier(.1, .2, .3, .4)", false); expect(match).toBeTruthy(); expectArraysToBeEqual(match, ["cubic-bezier(.1, .2, .3, .4)", ".1", ".2", ".3", ".4"]); }); it("should match bezier curve function in lax mode", function () { - match = TimingFunctionUtils.bezierCurveMatch("cubic-bezier(.1, .2, .3, .4)", true); + match = TimingFunctionUtils.timingFunctionMatch("cubic-bezier(.1, .2, .3, .4)", true); expect(match).toBeTruthy(); expectArraysToBeEqual(match, ["cubic-bezier(.1, .2, .3, .4)", ".1", ".2", ".3", ".4"]); }); it("should match bezier curve function with negative value", function () { - match = TimingFunctionUtils.bezierCurveMatch("cubic-bezier(0, -.2, 1, 1.2)", false); + match = TimingFunctionUtils.timingFunctionMatch("cubic-bezier(0, -.2, 1, 1.2)", false); expectArraysToBeEqual(match, ["cubic-bezier(0, -.2, 1, 1.2)", "0", "-.2", "1", "1.2"]); }); it("should match bezier curve function in full line of longhand css", function () { - match = TimingFunctionUtils.bezierCurveMatch(" transition-timing-function: cubic-bezier(.37, .28, .83, .94);", true); + match = TimingFunctionUtils.timingFunctionMatch(" transition-timing-function: cubic-bezier(.37, .28, .83, .94);", false); expectArraysToBeEqual(match, ["cubic-bezier(.37, .28, .83, .94)", ".37", ".28", ".83", ".94"]); }); it("should match bezier curve function in full line of shorthand css", function () { - match = TimingFunctionUtils.bezierCurveMatch(" transition: top 100ms cubic-bezier(.37, .28, .83, .94) 0;", true); + match = TimingFunctionUtils.timingFunctionMatch(" transition: top 100ms cubic-bezier(.37, .28, .83, .94) 0;", false); expectArraysToBeEqual(match, ["cubic-bezier(.37, .28, .83, .94)", ".37", ".28", ".83", ".94"]); }); it("should match bezier curve function with leading zeros", function () { - match = TimingFunctionUtils.bezierCurveMatch("cubic-bezier(0.1, 0.2, 0.3, 0.4)", false); + match = TimingFunctionUtils.timingFunctionMatch("cubic-bezier(0.1, 0.2, 0.3, 0.4)", false); expectArraysToBeEqual(match, ["cubic-bezier(0.1, 0.2, 0.3, 0.4)", "0.1", "0.2", "0.3", "0.4"]); }); it("should match bezier curve function with no optional whitespace", function () { - match = TimingFunctionUtils.bezierCurveMatch("cubic-bezier(.1,.2,.3,.4)", false); + match = TimingFunctionUtils.timingFunctionMatch("cubic-bezier(.1,.2,.3,.4)", false); expectArraysToBeEqual(match, ["cubic-bezier(.1,.2,.3,.4)", ".1", ".2", ".3", ".4"]); }); it("should match bezier curve function with extra optional whitespace", function () { - match = TimingFunctionUtils.bezierCurveMatch("cubic-bezier( .1 , .2 , .3 , .4 )", false); + match = TimingFunctionUtils.timingFunctionMatch("cubic-bezier( .1 , .2 , .3 , .4 )", false); expectArraysToBeEqual(match, ["cubic-bezier( .1 , .2 , .3 , .4 )", ".1", ".2", ".3", ".4"]); }); // Valid other functions it("should match linear function in declaration in strict mode", function () { - match = TimingFunctionUtils.bezierCurveMatch("transition-timing-function: linear;", false); + match = TimingFunctionUtils.timingFunctionMatch("transition-timing-function: linear;", false); expect(match.length).toEqual(1); expect(match[0]).toEqual("linear"); }); it("should match linear function value in lax mode", function () { - match = TimingFunctionUtils.bezierCurveMatch("linear", true); + match = TimingFunctionUtils.timingFunctionMatch("linear", true); expect(match.length).toEqual(1); expect(match[0]).toEqual("linear"); }); it("should match ease function in declaration in strict mode", function () { - match = TimingFunctionUtils.bezierCurveMatch("transition-timing-function: ease;", false); + match = TimingFunctionUtils.timingFunctionMatch("transition-timing-function: ease;", false); expect(match.length).toEqual(1); expect(match[0]).toEqual("ease"); }); it("should match ease function value in lax mode", function () { - match = TimingFunctionUtils.bezierCurveMatch("ease", true); + match = TimingFunctionUtils.timingFunctionMatch("ease", true); expect(match.length).toEqual(1); expect(match[0]).toEqual("ease"); }); it("should match ease-in function in declaration in strict mode", function () { - match = TimingFunctionUtils.bezierCurveMatch("transition-timing-function: ease-in;", false); + match = TimingFunctionUtils.timingFunctionMatch("transition-timing-function: ease-in;", false); expect(match.length).toEqual(1); expect(match[0]).toEqual("ease-in"); }); it("should match ease-in function value in lax mode", function () { - match = TimingFunctionUtils.bezierCurveMatch("ease-in", true); + match = TimingFunctionUtils.timingFunctionMatch("ease-in", true); expect(match.length).toEqual(1); expect(match[0]).toEqual("ease-in"); }); it("should match ease-out function in declaration in strict mode", function () { - match = TimingFunctionUtils.bezierCurveMatch("transition-timing-function: ease-out;", false); + match = TimingFunctionUtils.timingFunctionMatch("transition-timing-function: ease-out;", false); expect(match.length).toEqual(1); expect(match[0]).toEqual("ease-out"); }); it("should match ease-out function value in lax mode", function () { - match = TimingFunctionUtils.bezierCurveMatch("ease-out", true); + match = TimingFunctionUtils.timingFunctionMatch("ease-out", true); expect(match.length).toEqual(1); expect(match[0]).toEqual("ease-out"); }); it("should match ease-in-out function in declaration in strict mode", function () { - match = TimingFunctionUtils.bezierCurveMatch("transition-timing-function: ease-in-out;", false); + match = TimingFunctionUtils.timingFunctionMatch("transition-timing-function: ease-in-out;", false); expect(match.length).toEqual(1); expect(match[0]).toEqual("ease-in-out"); }); it("should match ease-in-out function value in lax mode", function () { - match = TimingFunctionUtils.bezierCurveMatch("ease-in-out", true); + match = TimingFunctionUtils.timingFunctionMatch("ease-in-out", true); expect(match.length).toEqual(1); expect(match[0]).toEqual("ease-in-out"); }); // Invalid cases it("should not match cubic-bezier function with out-of-range X parameters", function () { - match = TimingFunctionUtils.bezierCurveMatch("cubic-bezier(-.2, 0, 1.2, 1)", false); + match = TimingFunctionUtils.timingFunctionMatch("cubic-bezier(-.2, 0, 1.2, 1)", false); expect(match).toBeFalsy(); }); it("should not match cubic-bezier function with Infinity parameters", function () { - match = TimingFunctionUtils.bezierCurveMatch("cubic-bezier(0, Infinity, 1, -Infinity)", false); + match = TimingFunctionUtils.timingFunctionMatch("cubic-bezier(0, Infinity, 1, -Infinity)", false); expect(match).toBeFalsy(); }); it("should not match cubic-bezier function with non-numeric parameters", function () { - match = TimingFunctionUtils.bezierCurveMatch("cubic-bezier(x1, y1, x2, y2)", false); + match = TimingFunctionUtils.timingFunctionMatch("cubic-bezier(x1, y1, x2, y2)", false); expect(match).toBeFalsy(); }); it("should not match cubic-bezier function with no parameters", function () { - match = TimingFunctionUtils.bezierCurveMatch("cubic-bezier()", false); + match = TimingFunctionUtils.timingFunctionMatch("cubic-bezier()", false); expect(match).toBeFalsy(); }); it("should not match cubic-bezier function with 3 parameters", function () { - match = TimingFunctionUtils.bezierCurveMatch("cubic-bezier(0, 0, 1)", false); + match = TimingFunctionUtils.timingFunctionMatch("cubic-bezier(0, 0, 1)", false); expect(match).toBeFalsy(); }); it("should not match cubic-bezier function with 5 parameters", function () { - match = TimingFunctionUtils.bezierCurveMatch("cubic-bezier(0, 0, 1, 1, 1)", false); + match = TimingFunctionUtils.timingFunctionMatch("cubic-bezier(0, 0, 1, 1, 1)", false); expect(match).toBeFalsy(); }); it("should not match cubic-bezier function with invalid whitespace", function () { - match = TimingFunctionUtils.bezierCurveMatch("cubic-bezier (0, 0, 1, 1)", false); + match = TimingFunctionUtils.timingFunctionMatch("cubic-bezier (0, 0, 1, 1)", false); expect(match).toBeFalsy(); }); it("should not match cubic-bezier function with UPPER-CASE", function () { - match = TimingFunctionUtils.bezierCurveMatch("CUBIC-BEZIER(0, 0, 1, 1)", false); + match = TimingFunctionUtils.timingFunctionMatch("CUBIC-BEZIER(0, 0, 1, 1)", false); expect(match).toBeFalsy(); }); it("should not match unknown timing function", function () { - match = TimingFunctionUtils.bezierCurveMatch("ease-out-in", false); + match = TimingFunctionUtils.timingFunctionMatch("ease-out-in", false); expect(match).toBeFalsy(); }); it("should not match linear when not a timing function", function () { - match = TimingFunctionUtils.bezierCurveMatch("background: linear-gradient(to bottom, blue, white);", false); + match = TimingFunctionUtils.timingFunctionMatch("background: linear-gradient(to bottom, blue, white);", false); expect(match).toBeFalsy(); }); }); + describe("TimingFunctionUtils for step functions", function () { + var match; + + // Valid steps function cases + it("should match steps function in strict mode", function () { + match = TimingFunctionUtils.timingFunctionMatch("steps(3, start)", false); + expect(match).toBeTruthy(); + expectArraysToBeEqual(match, ["steps(3, start)", "3", "start"]); + }); + it("should match steps function in lax mode", function () { + match = TimingFunctionUtils.timingFunctionMatch("steps(3, start)", true); + expect(match).toBeTruthy(); + expectArraysToBeEqual(match, ["steps(3, start)", "3", "start"]); + }); + it("should match steps function with second parameter of end", function () { + match = TimingFunctionUtils.timingFunctionMatch("steps(12, end)", false); + expectArraysToBeEqual(match, ["steps(12, end)", "12", "end"]); + }); + it("should match steps function with only 1 parameter", function () { + match = TimingFunctionUtils.timingFunctionMatch("steps(8)", false); + expectArraysToBeEqual(match, ["steps(8)", "8", undefined]); + }); + it("should match steps function in full line of longhand css", function () { + match = TimingFunctionUtils.timingFunctionMatch(" transition-timing-function: steps(5, start);", false); + expectArraysToBeEqual(match, ["steps(5, start)", "5", "start"]); + }); + it("should match steps function in full line of shorthand css", function () { + match = TimingFunctionUtils.timingFunctionMatch(" transition: top 100ms steps(10) 0;", false); + expectArraysToBeEqual(match, ["steps(10)", "10", undefined]); + }); + it("should match steps function with leading zeros", function () { + match = TimingFunctionUtils.timingFunctionMatch("steps(04, end)", false); + expectArraysToBeEqual(match, ["steps(04, end)", "04", "end"]); + }); + it("should match steps function with no optional whitespace with 1 param", function () { + match = TimingFunctionUtils.timingFunctionMatch("steps(3)", false); + expectArraysToBeEqual(match, ["steps(3)", "3", undefined]); + }); + it("should match steps function with no optional whitespace with 2 params", function () { + match = TimingFunctionUtils.timingFunctionMatch("steps(3,end)", false); + expectArraysToBeEqual(match, ["steps(3,end)", "3", "end"]); + }); + it("should match steps function with extra optional whitespace with 1 param", function () { + match = TimingFunctionUtils.timingFunctionMatch("steps( 7 )", false); + expectArraysToBeEqual(match, ["steps( 7 )", "7", undefined]); + }); + it("should match steps function with extra optional whitespace with 2 params", function () { + match = TimingFunctionUtils.timingFunctionMatch("steps( 8 , start )", false); + expectArraysToBeEqual(match, ["steps( 8 , start )", "8", "start"]); + }); + + // Valid other functions + it("should match step-start function in declaration in strict mode", function () { + match = TimingFunctionUtils.timingFunctionMatch("transition-timing-function: step-start;", false); + expect(match.length).toEqual(1); + expect(match[0]).toEqual("step-start"); + }); + it("should match step-start function value in lax mode", function () { + match = TimingFunctionUtils.timingFunctionMatch("step-start", true); + expect(match.length).toEqual(1); + expect(match[0]).toEqual("step-start"); + }); + it("should match step-end function in declaration in strict mode", function () { + match = TimingFunctionUtils.timingFunctionMatch("transition-timing-function: step-end;", false); + expect(match.length).toEqual(1); + expect(match[0]).toEqual("step-end"); + }); + it("should match step-end function value in lax mode", function () { + match = TimingFunctionUtils.timingFunctionMatch("step-end", true); + expect(match.length).toEqual(1); + expect(match[0]).toEqual("step-end"); + }); + + // Invalid cases + it("should not match steps function with zero steps", function () { + match = TimingFunctionUtils.timingFunctionMatch("steps(0)", false); + expect(match).toBeFalsy(); + }); + it("should not match steps function with a non-integer number of steps", function () { + match = TimingFunctionUtils.timingFunctionMatch("steps(3.0)", false); + expect(match).toBeFalsy(); + }); + it("should not match steps function with a negative number of steps", function () { + match = TimingFunctionUtils.timingFunctionMatch("steps(-2)", false); + expect(match).toBeFalsy(); + }); + it("should not match steps function with an infinite number of steps", function () { + match = TimingFunctionUtils.timingFunctionMatch("steps(Infinity,)", false); + expect(match).toBeFalsy(); + }); + it("should not match steps function with NaN number of steps", function () { + match = TimingFunctionUtils.timingFunctionMatch("steps(NaN,)", false); + expect(match).toBeFalsy(); + }); + it("should not match steps function with non-numeric number of steps", function () { + match = TimingFunctionUtils.timingFunctionMatch("steps(x)", false); + expect(match).toBeFalsy(); + }); + it("should not match steps function with a string-value number of steps", function () { + match = TimingFunctionUtils.timingFunctionMatch("steps('3')", false); + expect(match).toBeFalsy(); + }); + it("should not match steps function with no parens", function () { + match = TimingFunctionUtils.timingFunctionMatch("steps", false); + expect(match).toBeFalsy(); + }); + it("should not match steps function with no parameters", function () { + match = TimingFunctionUtils.timingFunctionMatch("steps()", false); + expect(match).toBeFalsy(); + }); + it("should not match steps function with empty second parameter", function () { + match = TimingFunctionUtils.timingFunctionMatch("steps(1,)", false); + expect(match).toBeFalsy(); + }); + it("should not match steps function with undefined second parameter", function () { + match = TimingFunctionUtils.timingFunctionMatch("steps(1, middle)", false); + expect(match).toBeFalsy(); + }); + it("should not match steps function with 3 parameters", function () { + match = TimingFunctionUtils.timingFunctionMatch("steps(1, start, end)", false); + expect(match).toBeFalsy(); + }); + it("should not match steps function with invalid whitespace", function () { + match = TimingFunctionUtils.timingFunctionMatch("steps (1, end)", false); + expect(match).toBeFalsy(); + }); + it("should not match steps function with UPPER-CASE", function () { + match = TimingFunctionUtils.timingFunctionMatch("STEPS(12)", false); + expect(match).toBeFalsy(); + }); + it("should not match unknown timing function", function () { + match = TimingFunctionUtils.timingFunctionMatch("step", false); + expect(match).toBeFalsy(); + }); + }); + + describe("Bookmark Timing Function", function () { beforeEach(function () { var mock = SpecRunnerUtils.createMockEditor(testContentCSS, "css"); @@ -245,50 +383,80 @@ define(function (require, exports, module) { it("should bookmark second cubic-bezier() function when opened in inline editor", function () { testOpenTimingFunction({line: 13, ch: 80}, 75, 107); }); + it("should bookmark steps() function when opened in inline editor", function () { + testOpenTimingFunction({line: 17, ch: 37}, 32, 45); + }); + it("should bookmark step-start function when opened in inline editor", function () { + testOpenTimingFunction({line: 20, ch: 40}, 32, 42); + }); }); describe("TimingFunction editor UI", function () { - var timingFunctionEditor; + var timingFuncEditor; /** - * Creates a hidden TimingFunctionEditor and appends it to the body. Note that this is a - * standalone TimingFunctionEditor, not inside an InlineTimingFunctionEditor. + * Creates a hidden BezierCurveEditor and appends it to the body. Note that this is a + * standalone BezierCurveEditor, not inside an InlineTimingFunctionEditor. * @param {string} initialTimingFunction The timingFunction that should be initially set - * in the TimingFunctionEditor. - * @param {?function} callback An optional callback to be passed as the TimingFunctionEditor's + * in the BezierCurveEditor. + * @param {?function} callback An optional callback to be passed as the BezierCurveEditor's * callback. If none is supplied, a dummy function is passed. */ - function makeUI(initialTimingFunction, callback) { - timingFunctionEditor = new TimingFunctionEditor( - $(document.body), - TimingFunctionUtils.bezierCurveMatch(initialTimingFunction, true), - callback || function () { } - ); + function makeTimingFuncUI(initialTimingFunction, callback) { + var parent = $(document.body), + match = TimingFunctionUtils.timingFunctionMatch(initialTimingFunction, true), + cb = callback || function () { }; + + if (match.isBezier) { + timingFuncEditor = new BezierCurveEditor(parent, match, cb); + } else if (match.isStep) { + timingFuncEditor = new StepEditor(parent, match, cb); + } // Hide it - timingFunctionEditor.getRootElement().css("display", "none"); + timingFuncEditor.getRootElement().css("display", "none"); } afterEach(function () { - timingFunctionEditor.getRootElement().remove(); + timingFuncEditor.getRootElement().remove(); + timingFuncEditor = null; }); describe("Initial Load and External Update", function () { - it("should load the initial timing function correctly", function () { + it("should load the initial cubic-bezier function correctly", function () { runs(function () { - makeUI("cubic-bezier(.2, .3, .4, .5)"); - expect(timingFunctionEditor).toBeTruthy(); - expectArraysToBeEqual(timingFunctionEditor._cubicBezierCoords, [".2", ".3", ".4", ".5"]); + makeTimingFuncUI("cubic-bezier(.2, .3, .4, .5)"); + expect(timingFuncEditor).toBeTruthy(); + expect(timingFuncEditor._cubicBezierCoords).toBeTruthy(); + expectArraysToBeEqual(timingFuncEditor._cubicBezierCoords, [".2", ".3", ".4", ".5"]); }); }); - it("should load externally updated timing function correctly", function () { + it("should load externally updated cubic-bezier function correctly", function () { runs(function () { - makeUI("cubic-bezier(.1, .3, .5, .7)"); - var matchUpdate = TimingFunctionUtils.bezierCurveMatch("cubic-bezier(.2, .4, .6, .8)", true); - timingFunctionEditor.handleExternalUpdate(matchUpdate); - expectArraysToBeEqual(timingFunctionEditor._cubicBezierCoords, [".2", ".4", ".6", ".8"]); + makeTimingFuncUI("cubic-bezier(.1, .3, .5, .7)"); + var matchUpdate = TimingFunctionUtils.timingFunctionMatch("cubic-bezier(.2, .4, .6, .8)", true); + timingFuncEditor.handleExternalUpdate(matchUpdate); + expectArraysToBeEqual(timingFuncEditor._cubicBezierCoords, [".2", ".4", ".6", ".8"]); + }); + }); + it("should load the initial steps function correctly", function () { + runs(function () { + makeTimingFuncUI("steps(5, start)"); + expect(timingFuncEditor).toBeTruthy(); + expect(timingFuncEditor._stepParams).toBeTruthy(); + expect(timingFuncEditor._stepParams.count).toEqual(5); + expect(timingFuncEditor._stepParams.timing).toEqual("start"); + }); + }); + it("should load externally updated steps function correctly", function () { + runs(function () { + makeTimingFuncUI("steps(5, start)"); + var matchUpdate = TimingFunctionUtils.timingFunctionMatch("steps(6, end)", true); + timingFuncEditor.handleExternalUpdate(matchUpdate); + expect(timingFuncEditor._stepParams.count).toEqual(6); + expect(timingFuncEditor._stepParams.timing).toEqual("end"); }); }); }); @@ -297,32 +465,48 @@ define(function (require, exports, module) { it("should convert linear function to cubic-bezier function parameters", function () { runs(function () { - makeUI("linear"); - expectArraysToBeEqual(timingFunctionEditor._cubicBezierCoords, ["0", "0", "1", "1"]); + makeTimingFuncUI("linear"); + expectArraysToBeEqual(timingFuncEditor._cubicBezierCoords, ["0", "0", "1", "1"]); }); }); it("should convert ease function to cubic-bezier function parameters", function () { runs(function () { - makeUI("ease"); - expectArraysToBeEqual(timingFunctionEditor._cubicBezierCoords, [".25", ".1", ".25", "1"]); + makeTimingFuncUI("ease"); + expectArraysToBeEqual(timingFuncEditor._cubicBezierCoords, [".25", ".1", ".25", "1"]); }); }); it("should convert ease-in function to cubic-bezier function parameters", function () { runs(function () { - makeUI("ease-in"); - expectArraysToBeEqual(timingFunctionEditor._cubicBezierCoords, [".42", "0", "1", "1"]); + makeTimingFuncUI("ease-in"); + expectArraysToBeEqual(timingFuncEditor._cubicBezierCoords, [".42", "0", "1", "1"]); }); }); it("should convert ease-out function to cubic-bezier function parameters", function () { runs(function () { - makeUI("ease-out"); - expectArraysToBeEqual(timingFunctionEditor._cubicBezierCoords, ["0", "0", ".58", "1"]); + makeTimingFuncUI("ease-out"); + expectArraysToBeEqual(timingFuncEditor._cubicBezierCoords, ["0", "0", ".58", "1"]); }); }); it("should convert ease-in-out function to cubic-bezier function parameters", function () { runs(function () { - makeUI("ease-in-out"); - expectArraysToBeEqual(timingFunctionEditor._cubicBezierCoords, [".42", "0", ".58", "1"]); + makeTimingFuncUI("ease-in-out"); + expectArraysToBeEqual(timingFuncEditor._cubicBezierCoords, [".42", "0", ".58", "1"]); + }); + }); + it("should convert step-start function to steps function parameters", function () { + runs(function () { + makeTimingFuncUI("step-start"); + expect(timingFuncEditor).toBeTruthy(); + expect(timingFuncEditor._stepParams).toBeTruthy(); + expect(timingFuncEditor._stepParams.count).toEqual(1); + expect(timingFuncEditor._stepParams.timing).toEqual("start"); + }); + }); + it("should convert step-end function to steps function parameters", function () { + runs(function () { + makeTimingFuncUI("step-end"); + expect(timingFuncEditor._stepParams.count).toEqual(1); + expect(timingFuncEditor._stepParams.timing).toEqual("end"); }); }); }); @@ -360,40 +544,40 @@ define(function (require, exports, module) { /** * Test a mouse down event on the given UI element in a cubic-bezier function. * @param {object} opts The parameters to test: - * item: The (string) name of the member of TimingFunctionEditor that + * item: The (string) name of the member of BezierCurveEditor that * references the element to test. * clickAt: An [x, y] array specifying the simulated x/y mouse position as * an offset of the item's width/height. * expected: The expected array of values for _cubicBezierCoords. */ function testCubicBezierClick(opts) { - makeUI("cubic-bezier(.42, 0, .58 ,1)"); - var $item = $(timingFunctionEditor[opts.item]); + makeTimingFuncUI("cubic-bezier(.42, 0, .58 ,1)"); + var $item = $(timingFuncEditor[opts.item]); eventAtOffset("click", $item, opts.clickAt); - expectArraysToBeEqual(timingFunctionEditor._cubicBezierCoords, opts.expected); + expectArraysToBeEqual(timingFuncEditor._cubicBezierCoords, opts.expected); } /** * Test a drag event on the given UI element. * @param {object} opts The parameters to test: - * downItem: The (string) name of the member of TimingFunctionEditor + * downItem: The (string) name of the member of BezierCurveEditor * that references the element to mousedown on to drag. * clickAt: An [x, y] array specifying the simulated x/y mouse position as an offset of the * item's width/height. - * dragItem: The (string) name of the member of TimingFunctionEditor + * dragItem: The (string) name of the member of BezierCurveEditor * that references the element to drag item to. * dragTo: An [x, y] array specifying the location to drag to, using the same convention as clickAt. * expected: The expected array of values for _cubicBezierCoords. */ function testCubicBezierDrag(opts) { - makeUI("cubic-bezier(.42, 0, .58 ,1)"); - var $downItem = $(timingFunctionEditor[opts.downItem]), - $dragItem = $(timingFunctionEditor[opts.dragItem]); + makeTimingFuncUI("cubic-bezier(.42, 0, .58 ,1)"); + var $downItem = $(timingFuncEditor[opts.downItem]), + $dragItem = $(timingFuncEditor[opts.dragItem]); eventAtOffset("mousedown", $downItem, opts.clickAt); eventAtOffset("mousemove", $dragItem, opts.dragTo); $downItem.trigger("mouseup"); - expectArraysToBeEqual(timingFunctionEditor._cubicBezierCoords, opts.expected); + expectArraysToBeEqual(timingFuncEditor._cubicBezierCoords, opts.expected); } it("should move point P1 on mousedown in curve", function () { @@ -446,79 +630,79 @@ define(function (require, exports, module) { } /** - * Test a key event on the given UI element. + * Create a timing function editor and trigger a key event on it. * @param {object} opts The parameters to test: - * curve: The initial cubic-bezier curve - * item: The (string) name of the member of TimingFunctionEditor + * func: The initial timing function + * item: The (string) name of the member of BezierCurveEditor * that references the element to test. * key: The KeyEvent key code to simulate. * shift: Optional boolean specifying whether to simulate the shift * key being down (default false). * expected: The expected array of values for _cubicBezierCoords. */ - function testKey(opts) { - makeUI(opts.curve, opts.callback); - var $item = $(timingFunctionEditor[opts.item]); + function triggerTimingFunctionEditorKey(opts) { + makeTimingFuncUI(opts.func, opts.callback); + var $item = $(timingFuncEditor[opts.item]); $item.focus(); $item.trigger(makeKeyEvent(opts)); - expectArraysToBeEqual(timingFunctionEditor._cubicBezierCoords, opts.expected); } - it("should increase P1 x-value by .02 on right arrow", function () { - testKey({ - curve: "cubic-bezier(.42, 0, .58 ,1)", + // cubic-bezier() tests + it("should increase P1 x-value by .02 on right arrow in cubic-bezier()", function () { + triggerTimingFunctionEditorKey({ + func: "cubic-bezier(.42, 0, .58, 1)", item: "P1", key: KeyEvent.DOM_VK_RIGHT, - shift: false, - expected: [".44", "0", ".58", "1"] + shift: false }); + expectArraysToBeEqual(timingFuncEditor._cubicBezierCoords, [".44", "0", ".58", "1"]); }); - it("should increase P1 y-value by .1 on shift up arrow", function () { - testKey({ - curve: "cubic-bezier(.42, 0, .58 ,1)", + it("should increase P1 y-value by .1 on shift up arrow in cubic-bezier()", function () { + triggerTimingFunctionEditorKey({ + func: "cubic-bezier(.42, 0, .58, 1)", item: "P1", key: KeyEvent.DOM_VK_UP, - shift: true, - expected: [".42", ".1", ".58", "1"] + shift: true }); + expectArraysToBeEqual(timingFuncEditor._cubicBezierCoords, [".42", ".1", ".58", "1"]); }); - it("should decrease P2 x-value by .02 on left arrow", function () { - testKey({ - curve: "cubic-bezier(.42, 0, .58 ,1)", + it("should decrease P2 x-value by .02 on left arrow in cubic-bezier()", function () { + triggerTimingFunctionEditorKey({ + func: "cubic-bezier(.42, 0, .58, 1)", item: "P2", key: KeyEvent.DOM_VK_LEFT, - shift: false, - expected: [".42", "0", ".56", "1"] + shift: false }); + expectArraysToBeEqual(timingFuncEditor._cubicBezierCoords, [".42", "0", ".56", "1"]); }); - it("should decrease P2 y-value by .1 on shift down arrow", function () { - testKey({ - curve: "cubic-bezier(.42, 0, .58 ,1)", + it("should decrease P2 y-value by .1 on shift down arrow in cubic-bezier()", function () { + triggerTimingFunctionEditorKey({ + func: "cubic-bezier(.42, 0, .58 ,1)", item: "P2", key: KeyEvent.DOM_VK_DOWN, - shift: true, - expected: [".42", "0", ".58", ".9"] + shift: true }); + expectArraysToBeEqual(timingFuncEditor._cubicBezierCoords, [".42", "0", ".58", ".9"]); }); - it("should not decrease P1 x-value below 0 on left arrow", function () { - testKey({ - curve: "cubic-bezier(0, 0, 1 ,1)", + it("should not decrease P1 x-value below 0 on left arrow in cubic-bezier()", function () { + triggerTimingFunctionEditorKey({ + func: "cubic-bezier(0, 0, 1, 1)", item: "P1", key: KeyEvent.DOM_VK_LEFT, - shift: false, - expected: ["0", "0", "1", "1"] + shift: false }); + expectArraysToBeEqual(timingFuncEditor._cubicBezierCoords, ["0", "0", "1", "1"]); }); - it("should not increase P2 x-value above 0 on shift right arrow", function () { - testKey({ - curve: "cubic-bezier(0, 0, 1 ,1)", + it("should not increase P2 x-value above 0 on shift right arrow in cubic-bezier()", function () { + triggerTimingFunctionEditorKey({ + func: "cubic-bezier(0, 0, 1, 1)", item: "P2", key: KeyEvent.DOM_VK_RIGHT, - shift: true, - expected: ["0", "0", "1", "1"] + shift: true }); + expectArraysToBeEqual(timingFuncEditor._cubicBezierCoords, ["0", "0", "1", "1"]); }); - it("should call callback function after edit", function () { + it("should call callback function after cubic-bezier edit in cubic-bezier()", function () { var calledBack = false; var _callback = function (timingFunctionString) { @@ -527,14 +711,95 @@ define(function (require, exports, module) { }; runs(function () { - testKey({ - curve: "cubic-bezier(.42, 0, .58 ,1)", + triggerTimingFunctionEditorKey({ + func: "cubic-bezier(.42, 0, .58 ,1)", item: "P1", key: KeyEvent.DOM_VK_UP, shift: true, - expected: [".42", ".1", ".58", "1"], callback: _callback }); + expectArraysToBeEqual(timingFuncEditor._cubicBezierCoords, [".42", ".1", ".58", "1"]); + }); + + runs(function () { + expect(calledBack).toBeTruthy(); + }); + }); + + // steps() tests + it("should increase count by 1 on up arrow in steps()", function () { + triggerTimingFunctionEditorKey({ + func: "steps(5)", + item: "canvas", + key: KeyEvent.DOM_VK_UP + }); + expect(timingFuncEditor._stepParams.count).toEqual(6); + }); + it("should decrease count by 1 on down arrow in steps()", function () { + triggerTimingFunctionEditorKey({ + func: "steps(5)", + item: "canvas", + key: KeyEvent.DOM_VK_DOWN + }); + expect(timingFuncEditor._stepParams.count).toEqual(4); + }); + it("should change start to end on right arrow in steps()", function () { + triggerTimingFunctionEditorKey({ + func: "steps(5, start)", + item: "canvas", + key: KeyEvent.DOM_VK_RIGHT + }); + expect(timingFuncEditor._stepParams.timing).toEqual("end"); + }); + it("should change end to start on left arrow in steps()", function () { + triggerTimingFunctionEditorKey({ + func: "steps(5, end)", + item: "canvas", + key: KeyEvent.DOM_VK_LEFT + }); + expect(timingFuncEditor._stepParams.timing).toEqual("start"); + }); + it("should not decrease count to be less than 1 on down arrow in steps()", function () { + triggerTimingFunctionEditorKey({ + func: "steps(1)", + item: "canvas", + key: KeyEvent.DOM_VK_DOWN + }); + expect(timingFuncEditor._stepParams.count).toEqual(1); + }); + it("should not change start to end on left arrow in steps()", function () { + triggerTimingFunctionEditorKey({ + func: "steps(5, start)", + item: "canvas", + key: KeyEvent.DOM_VK_LEFT + }); + expect(timingFuncEditor._stepParams.timing).toEqual("start"); + }); + it("should not change end to start on right arrow in steps()", function () { + triggerTimingFunctionEditorKey({ + func: "steps(5, end)", + item: "canvas", + key: KeyEvent.DOM_VK_RIGHT + }); + expect(timingFuncEditor._stepParams.timing).toEqual("end"); + }); + + it("should call callback function after steps function edit", function () { + var calledBack = false; + + var _callback = function (timingFunctionString) { + calledBack = true; + expect(timingFunctionString).toBe("steps(5, start)"); + }; + + runs(function () { + triggerTimingFunctionEditorKey({ + func: "steps(4, start)", + item: "canvas", + key: KeyEvent.DOM_VK_UP, + callback: _callback + }); + expect(timingFuncEditor._stepParams.count).toEqual(5); }); runs(function () { diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index f0afcf4bf75..487b661f585 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -477,6 +477,8 @@ define({ // extensions/default/InlineTimingFunctionEditor "INLINE_TIMING_EDITOR_TIME" : "Time", "INLINE_TIMING_EDITOR_PROGRESSION" : "Progression", + "BEZIER_EDITOR_INFO" : " Move selected point
Shift Move by ten units", + "STEPS_EDITOR_INFO" : " Increase or decrease steps
'Start' or 'End'", // extensions/default/InlineColorEditor "COLOR_EDITOR_CURRENT_COLOR_SWATCH_TIP" : "Current Color",