Bug 1134568 - Implement a preset gallery for CubicBezierWidget incl. 8 preset functions for categories: ease-in, ease-out, and ease-in-out. r=pbrosset

This commit is contained in:
John Giannakos 2015-03-23 08:01:00 -04:00
Родитель b2c4fc1141
Коммит a300673a9a
15 изменённых файлов: 862 добавлений и 136 удалений

Просмотреть файл

@ -61,6 +61,7 @@ EXTRA_JS_MODULES.devtools.shared += [
]
EXTRA_JS_MODULES.devtools.shared.widgets += [
'widgets/CubicBezierPresets.js',
'widgets/CubicBezierWidget.js',
'widgets/FastListWidget.js',
'widgets/Spectrum.js',

Просмотреть файл

@ -15,6 +15,9 @@ support-files =
[browser_cubic-bezier-01.js]
[browser_cubic-bezier-02.js]
[browser_cubic-bezier-03.js]
[browser_cubic-bezier-04.js]
[browser_cubic-bezier-05.js]
[browser_cubic-bezier-06.js]
[browser_flame-graph-01.js]
[browser_flame-graph-02.js]
[browser_flame-graph-03a.js]

Просмотреть файл

@ -7,16 +7,20 @@
// Tests that the CubicBezierWidget generates content in a given parent node
const TEST_URI = "chrome://browser/content/devtools/cubic-bezier-frame.xhtml";
const {CubicBezierWidget} = devtools.require("devtools/shared/widgets/CubicBezierWidget");
const {CubicBezierWidget} =
devtools.require("devtools/shared/widgets/CubicBezierWidget");
add_task(function*() {
yield promiseTab("about:blank");
let [host, win, doc] = yield createHost("bottom", TEST_URI);
info("Checking that the markup is created in the parent");
info("Checking that the graph markup is created in the parent");
let container = doc.querySelector("#container");
let w = new CubicBezierWidget(container);
ok(container.querySelector(".display-wrap"),
"The display has been added");
ok(container.querySelector(".coordinate-plane"),
"The coordinate plane has been added");
let buttons = container.querySelectorAll("button");

Просмотреть файл

@ -7,26 +7,37 @@
// Tests the CubicBezierWidget events
const TEST_URI = "chrome://browser/content/devtools/cubic-bezier-frame.xhtml";
const {CubicBezierWidget, PREDEFINED} =
const {CubicBezierWidget} =
devtools.require("devtools/shared/widgets/CubicBezierWidget");
const {PREDEFINED} = require("devtools/shared/widgets/CubicBezierPresets");
add_task(function*() {
yield promiseTab("about:blank");
let [host, win, doc] = yield createHost("bottom", TEST_URI);
// Required or widget will be clipped inside of 'bottom'
// host by -14. Setting `fixed` zeroes this which is needed for
// calculating offsets. Occurs in test env only.
doc.body.setAttribute("style", "position: fixed");
let container = doc.querySelector("#container");
let w = new CubicBezierWidget(container, PREDEFINED.linear);
yield pointsCanBeDragged(w, win, doc);
yield curveCanBeClicked(w, win, doc);
yield pointsCanBeMovedWithKeyboard(w, win, doc);
let rect = w.curve.getBoundingClientRect();
rect.graphTop = rect.height * w.bezierCanvas.padding[0];
rect.graphBottom = rect.height - rect.graphTop;
rect.graphHeight = rect.graphBottom - rect.graphTop;
yield pointsCanBeDragged(w, win, doc, rect);
yield curveCanBeClicked(w, win, doc, rect);
yield pointsCanBeMovedWithKeyboard(w, win, doc, rect);
w.destroy();
host.destroy();
gBrowser.removeCurrentTab();
});
function* pointsCanBeDragged(widget, win, doc) {
function* pointsCanBeDragged(widget, win, doc, offsets) {
info("Checking that the control points can be dragged with the mouse");
info("Listening for the update event");
@ -34,7 +45,7 @@ function* pointsCanBeDragged(widget, win, doc) {
info("Generating a mousedown/move/up on P1");
widget._onPointMouseDown({target: widget.p1});
doc.onmousemove({pageX: 0, pageY: 100});
doc.onmousemove({pageX: offsets.left, pageY: offsets.graphTop});
doc.onmouseup();
let bezier = yield onUpdated;
@ -48,7 +59,7 @@ function* pointsCanBeDragged(widget, win, doc) {
info("Generating a mousedown/move/up on P2");
widget._onPointMouseDown({target: widget.p2});
doc.onmousemove({pageX: 200, pageY: 300});
doc.onmousemove({pageX: offsets.right, pageY: offsets.graphBottom});
doc.onmouseup();
bezier = yield onUpdated;
@ -56,14 +67,16 @@ function* pointsCanBeDragged(widget, win, doc) {
is(bezier.P2[1], 0, "The new P2 progress coordinate is correct");
}
function* curveCanBeClicked(widget, win, doc) {
function* curveCanBeClicked(widget, win, doc, offsets) {
info("Checking that clicking on the curve moves the closest control point");
info("Listening for the update event");
let onUpdated = widget.once("updated");
info("Click close to P1");
widget._onCurveClick({pageX: 50, pageY: 150});
let x = offsets.left + (offsets.width / 4.0);
let y = offsets.graphTop + (offsets.graphHeight / 4.0);
widget._onCurveClick({pageX: x, pageY: y});
let bezier = yield onUpdated;
ok(true, "The widget fired the updated event");
@ -76,7 +89,9 @@ function* curveCanBeClicked(widget, win, doc) {
onUpdated = widget.once("updated");
info("Click close to P2");
widget._onCurveClick({pageX: 150, pageY: 250});
x = offsets.right - (offsets.width / 4);
y = offsets.graphBottom - (offsets.graphHeight / 4);
widget._onCurveClick({pageX: x, pageY: y});
bezier = yield onUpdated;
is(bezier.P2[0], 0.75, "The new P2 time coordinate is correct");
@ -85,57 +100,89 @@ function* curveCanBeClicked(widget, win, doc) {
is(bezier.P1[1], 0.75, "P1 progress coordinate remained unchanged");
}
function* pointsCanBeMovedWithKeyboard(widget, win, doc) {
function* pointsCanBeMovedWithKeyboard(widget, win, doc, offsets) {
info("Checking that points respond to keyboard events");
let singleStep = 3;
let shiftStep = 30;
info("Moving P1 to the left");
let newOffset = parseInt(widget.p1.style.left) - singleStep;
let x = widget.bezierCanvas.
offsetsToCoordinates({style: {left: newOffset}})[0];
let onUpdated = widget.once("updated");
widget._onPointKeyDown(getKeyEvent(widget.p1, 37));
let bezier = yield onUpdated;
is(bezier.P1[0], 0.235, "The new P1 time coordinate is correct");
is(bezier.P1[0], x, "The new P1 time coordinate is correct");
is(bezier.P1[1], 0.75, "The new P1 progress coordinate is correct");
info("Moving P1 to the left, fast");
newOffset = parseInt(widget.p1.style.left) - shiftStep;
x = widget.bezierCanvas.
offsetsToCoordinates({style: {left: newOffset}})[0];
onUpdated = widget.once("updated");
widget._onPointKeyDown(getKeyEvent(widget.p1, 37, true));
bezier = yield onUpdated;
is(bezier.P1[0], 0.085, "The new P1 time coordinate is correct");
is(bezier.P1[0], x, "The new P1 time coordinate is correct");
is(bezier.P1[1], 0.75, "The new P1 progress coordinate is correct");
info("Moving P1 to the right, fast");
newOffset = parseInt(widget.p1.style.left) + shiftStep;
x = widget.bezierCanvas.
offsetsToCoordinates({style: {left: newOffset}})[0];
onUpdated = widget.once("updated");
widget._onPointKeyDown(getKeyEvent(widget.p1, 39, true));
bezier = yield onUpdated;
is(bezier.P1[0], 0.235, "The new P1 time coordinate is correct");
is(bezier.P1[0], x, "The new P1 time coordinate is correct");
is(bezier.P1[1], 0.75, "The new P1 progress coordinate is correct");
info("Moving P1 to the bottom");
newOffset = parseInt(widget.p1.style.top) + singleStep;
let y = widget.bezierCanvas.
offsetsToCoordinates({style: {top: newOffset}})[1];
onUpdated = widget.once("updated");
widget._onPointKeyDown(getKeyEvent(widget.p1, 40));
bezier = yield onUpdated;
is(bezier.P1[0], 0.235, "The new P1 time coordinate is correct");
is(bezier.P1[1], 0.735, "The new P1 progress coordinate is correct");
is(bezier.P1[0], x, "The new P1 time coordinate is correct");
is(bezier.P1[1], y, "The new P1 progress coordinate is correct");
info("Moving P1 to the bottom, fast");
newOffset = parseInt(widget.p1.style.top) + shiftStep;
y = widget.bezierCanvas.
offsetsToCoordinates({style: {top: newOffset}})[1];
onUpdated = widget.once("updated");
widget._onPointKeyDown(getKeyEvent(widget.p1, 40, true));
bezier = yield onUpdated;
is(bezier.P1[0], 0.235, "The new P1 time coordinate is correct");
is(bezier.P1[1], 0.585, "The new P1 progress coordinate is correct");
is(bezier.P1[0], x, "The new P1 time coordinate is correct");
is(bezier.P1[1], y, "The new P1 progress coordinate is correct");
info("Moving P1 to the top, fast");
newOffset = parseInt(widget.p1.style.top) - shiftStep;
y = widget.bezierCanvas.
offsetsToCoordinates({style: {top: newOffset}})[1];
onUpdated = widget.once("updated");
widget._onPointKeyDown(getKeyEvent(widget.p1, 38, true));
bezier = yield onUpdated;
is(bezier.P1[0], 0.235, "The new P1 time coordinate is correct");
is(bezier.P1[1], 0.735, "The new P1 progress coordinate is correct");
is(bezier.P1[0], x, "The new P1 time coordinate is correct");
is(bezier.P1[1], y, "The new P1 progress coordinate is correct");
info("Checking that keyboard events also work with P2");
info("Moving P2 to the left");
newOffset = parseInt(widget.p2.style.left) - singleStep;
x = widget.bezierCanvas.
offsetsToCoordinates({style: {left: newOffset}})[0];
onUpdated = widget.once("updated");
widget._onPointKeyDown(getKeyEvent(widget.p2, 37));
bezier = yield onUpdated;
is(bezier.P2[0], 0.735, "The new P2 time coordinate is correct");
is(bezier.P2[0], x, "The new P2 time coordinate is correct");
is(bezier.P2[1], 0.25, "The new P2 progress coordinate is correct");
}

Просмотреть файл

@ -7,8 +7,9 @@
// Tests that coordinates can be changed programatically in the CubicBezierWidget
const TEST_URI = "chrome://browser/content/devtools/cubic-bezier-frame.xhtml";
const {CubicBezierWidget, PREDEFINED} =
const {CubicBezierWidget} =
devtools.require("devtools/shared/widgets/CubicBezierWidget");
const {PREDEFINED} = require("devtools/shared/widgets/CubicBezierPresets");
add_task(function*() {
yield promiseTab("about:blank");

Просмотреть файл

@ -0,0 +1,51 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that the CubicBezierPresetWidget generates markup.
const TEST_URI = "chrome://browser/content/devtools/cubic-bezier-frame.xhtml";
const {CubicBezierPresetWidget} =
devtools.require("devtools/shared/widgets/CubicBezierWidget");
const {PRESETS} = require("devtools/shared/widgets/CubicBezierPresets");
add_task(function*() {
yield promiseTab("about:blank");
let [host, win, doc] = yield createHost("bottom", TEST_URI);
let container = doc.querySelector("#container");
let w = new CubicBezierPresetWidget(container);
info("Checking that the presets are created in the parent");
ok(container.querySelector(".preset-pane"),
"The preset pane has been added");
ok(container.querySelector("#preset-categories"),
"The preset categories have been added");
let categories = container.querySelectorAll(".category");
is(categories.length, Object.keys(PRESETS).length,
"The preset categories have been added");
Object.keys(PRESETS).forEach(category => {
ok(container.querySelector("#" + category), `${category} has been added`);
ok(container.querySelector("#preset-category-" + category),
`The preset list for ${category} has been added.`);
});
info("Checking that each of the presets and its preview have been added");
Object.keys(PRESETS).forEach(category => {
Object.keys(PRESETS[category]).forEach(presetLabel => {
let preset = container.querySelector("#" + presetLabel);
ok(preset, `${presetLabel} has been added`);
ok(preset.querySelector("canvas"),
`${presetLabel}'s canvas preview has been added`);
ok(preset.querySelector("p"),
`${presetLabel}'s label has been added`);
});
});
w.destroy();
host.destroy();
gBrowser.removeCurrentTab();
});

Просмотреть файл

@ -0,0 +1,49 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that the CubicBezierPresetWidget cycles menus
const TEST_URI = "chrome://browser/content/devtools/cubic-bezier-frame.xhtml";
const {CubicBezierPresetWidget} =
devtools.require("devtools/shared/widgets/CubicBezierWidget");
const {PREDEFINED, PRESETS, DEFAULT_PRESET_CATEGORY} =
require("devtools/shared/widgets/CubicBezierPresets");
add_task(function*() {
yield promiseTab("about:blank");
let [host, win, doc] = yield createHost("bottom", TEST_URI);
let container = doc.querySelector("#container");
let w = new CubicBezierPresetWidget(container);
info("Checking that preset is selected if coordinates are known");
w.refreshMenu([0, 0, 0, 0]);
is(w.activeCategory, container.querySelector(`#${DEFAULT_PRESET_CATEGORY}`),
"The default category is selected");
is(w._activePreset, null, "There is no selected category");
w.refreshMenu(PREDEFINED["linear"]);
is(w.activeCategory, container.querySelector("#ease-in-out"),
"The ease-in-out category is active");
is(w._activePreset, container.querySelector("#ease-in-out-linear"),
"The ease-in-out-linear preset is active");
w.refreshMenu(PRESETS["ease-out"]["ease-out-sine"]);
is(w.activeCategory, container.querySelector("#ease-out"),
"The ease-out category is active");
is(w._activePreset, container.querySelector("#ease-out-sine"),
"The ease-out-sine preset is active");
w.refreshMenu([0, 0, 0, 0]);
is(w.activeCategory, container.querySelector("#ease-out"),
"The ease-out category is still active");
is(w._activePreset, null, "No preset is active");
w.destroy();
host.destroy();
gBrowser.removeCurrentTab();
});

Просмотреть файл

@ -0,0 +1,80 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests the integration between CubicBezierWidget and CubicBezierPresets
const TEST_URI = "chrome://browser/content/devtools/cubic-bezier-frame.xhtml";
const {CubicBezierWidget} =
devtools.require("devtools/shared/widgets/CubicBezierWidget");
const {PRESETS} = require("devtools/shared/widgets/CubicBezierPresets");
add_task(function*() {
yield promiseTab("about:blank");
let [host, win, doc] = yield createHost("bottom", TEST_URI);
let container = doc.querySelector("#container");
let w = new CubicBezierWidget(container,
PRESETS["ease-in"]["ease-in-sine"]);
w.presets.refreshMenu(PRESETS["ease-in"]["ease-in-sine"]);
let rect = w.curve.getBoundingClientRect();
rect.graphTop = rect.height * w.bezierCanvas.padding[0];
yield adjustingBezierUpdatesPreset(w, win, doc, rect);
yield selectingPresetUpdatesBezier(w, win, doc, rect);
w.destroy();
host.destroy();
gBrowser.removeCurrentTab();
});
function* adjustingBezierUpdatesPreset(widget, win, doc, rect) {
info("Checking that changing the bezier refreshes the preset menu");
is(widget.presets.activeCategory,
doc.querySelector("#ease-in"),
"The selected category is ease-in");
is(widget.presets._activePreset,
doc.querySelector("#ease-in-sine"),
"The selected preset is ease-in-sine");
info("Generating custom bezier curve by dragging");
widget._onPointMouseDown({target: widget.p1});
doc.onmousemove({pageX: rect.left, pageY: rect.graphTop});
doc.onmouseup();
is(widget.presets.activeCategory,
doc.querySelector("#ease-in"),
"The selected category is still ease-in");
is(widget.presets._activePreset, null,
"There is no active preset");
}
function* selectingPresetUpdatesBezier(widget, win, doc, rect) {
info("Checking that selecting a preset updates bezier curve");
info("Listening for the new coordinates event");
let onNewCoordinates = widget.presets.once("new-coordinates");
let onUpdated = widget.once("updated");
info("Click a preset");
let preset = doc.querySelector("#ease-in-sine");
widget.presets._onPresetClick({currentTarget: preset});
yield onNewCoordinates;
ok(true, "The preset widget fired the new-coordinates event");
let bezier = yield onUpdated;
ok(true, "The bezier canvas fired the updated event");
is(bezier.P1[0], preset.coordinates[0], "The new P1 time coordinate is correct");
is(bezier.P1[1], preset.coordinates[1], "The new P1 progress coordinate is correct");
is(bezier.P2[0], preset.coordinates[2], "P2 time coordinate is correct ");
is(bezier.P2[1], preset.coordinates[3], "P2 progress coordinate is correct");
}

Просмотреть файл

@ -104,7 +104,10 @@ function getCanvasMock(w=200, h=400) {
stroke: () => {},
arc: () => {},
fill: () => {},
bezierCurveTo: () => {}
bezierCurveTo: () => {},
save: () => {},
restore: () => {},
setTransform: () => {}
};
},
width: w,

Просмотреть файл

@ -19,6 +19,7 @@ function run_test() {
coordinatesToStringOutputsAString();
pointGettersReturnPointCoordinatesArrays();
toStringOutputsCubicBezierValue();
toStringOutputsCssPresetValues();
}
function throwsWhenMissingCoordinates() {
@ -84,8 +85,27 @@ function pointGettersReturnPointCoordinatesArrays() {
function toStringOutputsCubicBezierValue() {
do_print("toString() outputs the cubic-bezier() value");
let c = new CubicBezier([0, 1, 1, 0]);
do_check_eq(c.toString(), "cubic-bezier(0,1,1,0)");
}
function toStringOutputsCssPresetValues() {
do_print("toString() outputs the css predefined values");
let c = new CubicBezier([0, 0, 1, 1]);
do_check_eq(c.toString(), "cubic-bezier(0,0,1,1)");
do_check_eq(c.toString(), "linear");
c = new CubicBezier([0.25, 0.1, 0.25, 1]);
do_check_eq(c.toString(), "ease");
c = new CubicBezier([0.42, 0, 1, 1]);
do_check_eq(c.toString(), "ease-in");
c = new CubicBezier([0, 0, 0.58, 1]);
do_check_eq(c.toString(), "ease-out");
c = new CubicBezier([0.42, 0, 0.58, 1]);
do_check_eq(c.toString(), "ease-in-out");
}
function do_check_throws(cb, info) {

Просмотреть файл

@ -0,0 +1,64 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
// Set of preset definitions for use with CubicBezierWidget
// Credit: http://easings.net
"use strict";
const PREDEFINED = {
"ease": [0.25, 0.1, 0.25, 1],
"linear": [0, 0, 1, 1],
"ease-in": [0.42, 0, 1, 1],
"ease-out": [0, 0, 0.58, 1],
"ease-in-out": [0.42, 0, 0.58, 1]
};
const PRESETS = {
"ease-in": {
"ease-in-linear": [0, 0, 1, 1],
"ease-in-ease-in": [0.42, 0, 1, 1],
"ease-in-sine": [0.47, 0, 0.74, 0.71],
"ease-in-quadratic": [0.55, 0.09, 0.68, 0.53],
"ease-in-cubic": [0.55, 0.06, 0.68, 0.19],
"ease-in-quartic": [0.9, 0.03, 0.69, 0.22],
"ease-in-quintic": [0.76, 0.05, 0.86, 0.06],
"ease-in-exponential": [0.95, 0.05, 0.8, 0.04],
"ease-in-circular": [0.6, 0.04, 0.98, 0.34],
"ease-in-backward": [0.6, -0.28, 0.74, 0.05]
},
"ease-out": {
"ease-out-linear": [0, 0, 1, 1],
"ease-out-ease-out": [0, 0, 0.58, 1],
"ease-out-sine": [0.39, 0.58, 0.57, 1],
"ease-out-quadratic": [0.25, 0.46, 0.45, 0.94],
"ease-out-cubic": [0.22, 0.61, 0.36, 1],
"ease-out-quartic": [0.17, 0.84, 0.44, 1],
"ease-out-quintic": [0.23, 1, 0.32, 1],
"ease-out-exponential": [0.19, 1, 0.22, 1],
"ease-out-circular": [0.08, 0.82, 0.17, 1],
"ease-out-backward": [0.18, 0.89, 0.32, 1.28]
},
"ease-in-out": {
"ease-in-out-linear": [0, 0, 1, 1],
"ease-in-out-ease": [0.25, 0.1, 0.25, 1],
"ease-in-out-ease-in-out": [0.42, 0, 0.58, 1],
"ease-in-out-sine": [0.45, 0.05, 0.55, 0.95],
"ease-in-out-quadratic": [0.46, 0.03, 0.52, 0.96],
"ease-in-out-cubic": [0.65, 0.05, 0.36, 1],
"ease-in-out-quartic": [0.77, 0, 0.18, 1],
"ease-in-out-quintic": [0.86, 0, 0.07, 1],
"ease-in-out-exponential": [1, 0, 0, 1],
"ease-in-out-circular": [0.79, 0.14, 0.15, 0.86],
"ease-in-out-backward": [0.68, -0.55, 0.27, 1.55]
}
};
const DEFAULT_PRESET_CATEGORY = Object.keys(PRESETS)[0];
exports.PRESETS = PRESETS;
exports.PREDEFINED = PREDEFINED;
exports.DEFAULT_PRESET_CATEGORY = DEFAULT_PRESET_CATEGORY;

Просмотреть файл

@ -27,14 +27,7 @@
const EventEmitter = require("devtools/toolkit/event-emitter");
const {setTimeout, clearTimeout} = require("sdk/timers");
const PREDEFINED = exports.PREDEFINED = {
"ease": [.25, .1, .25, 1],
"linear": [0, 0, 1, 1],
"ease-in": [.42, 0, 1, 1],
"ease-out": [0, 0, .58, 1],
"ease-in-out": [.42, 0, .58, 1]
};
const {PREDEFINED, PRESETS, DEFAULT_PRESET_CATEGORY} = require("devtools/shared/widgets/CubicBezierPresets");
/**
* CubicBezier data structure helper
@ -59,7 +52,7 @@ function CubicBezier(coordinates) {
return this.map(n => {
return (Math.round(n * 100)/100 + '').replace(/^0\./, '.');
}) + "";
}
};
}
exports.CubicBezier = CubicBezier;
@ -74,7 +67,11 @@ CubicBezier.prototype = {
},
toString: function() {
return 'cubic-bezier(' + this.coordinates + ')';
// Check first if current coords are one of css predefined functions
let predefName = Object.keys(PREDEFINED)
.find(key => coordsAreEqual(PREDEFINED[key], this.coordinates));
return predefName || 'cubic-bezier(' + this.coordinates + ')';
}
};
@ -97,7 +94,7 @@ function BezierCanvas(canvas, bezier, padding) {
-canvas.height * (1 - p[0] - p[2]));
this.ctx.translate(p[3] / (1 - p[1] - p[3]),
-1 - p[0] / (1 - p[0] - p[2]));
};
}
exports.BezierCanvas = BezierCanvas;
@ -115,7 +112,7 @@ BezierCanvas.prototype = {
}, {
left: w * (this.bezier.coordinates[2] * (1 - p[3] - p[1]) - p[3]) + 'px',
top: h * (1 - this.bezier.coordinates[3] * (1 - p[0] - p[2]) - p[0]) + 'px'
}]
}];
},
/**
@ -128,8 +125,8 @@ BezierCanvas.prototype = {
p = p.map(function(a, i) { return a * (i % 2? w : h)});
return [
(parseInt(element.style.left) - p[3]) / (w + p[1] + p[3]),
(h - parseInt(element.style.top) - p[2]) / (h - p[0] - p[2])
(parseFloat(element.style.left) - p[3]) / (w + p[1] + p[3]),
(h - parseFloat(element.style.top) - p[2]) / (h - p[0] - p[2])
];
},
@ -143,40 +140,48 @@ BezierCanvas.prototype = {
handleColor: '#666',
handleThickness: .008,
bezierColor: '#4C9ED9',
bezierThickness: .015
bezierThickness: .015,
drawHandles: true
};
for (let setting in settings) {
defaultSettings[setting] = settings[setting];
}
this.ctx.clearRect(-.5,-.5, 2, 2);
// Clear the canvas –making sure to clear the
// whole area by resetting the transform first.
this.ctx.save();
this.ctx.setTransform(1, 0, 0, 1, 0, 0);
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.ctx.restore();
// Draw control handles
this.ctx.beginPath();
this.ctx.fillStyle = defaultSettings.handleColor;
this.ctx.lineWidth = defaultSettings.handleThickness;
this.ctx.strokeStyle = defaultSettings.handleColor;
if (defaultSettings.drawHandles) {
// Draw control handles
this.ctx.beginPath();
this.ctx.fillStyle = defaultSettings.handleColor;
this.ctx.lineWidth = defaultSettings.handleThickness;
this.ctx.strokeStyle = defaultSettings.handleColor;
this.ctx.moveTo(0, 0);
this.ctx.lineTo(xy[0], xy[1]);
this.ctx.moveTo(1,1);
this.ctx.lineTo(xy[2], xy[3]);
this.ctx.moveTo(0, 0);
this.ctx.lineTo(xy[0], xy[1]);
this.ctx.moveTo(1,1);
this.ctx.lineTo(xy[2], xy[3]);
this.ctx.stroke();
this.ctx.closePath();
this.ctx.stroke();
this.ctx.closePath();
function circle(ctx, cx, cy, r) {
return ctx.beginPath();
ctx.arc(cx, cy, r, 0, 2*Math.PI, !1);
ctx.closePath();
var circle = function(ctx, cx, cy, r) {
return ctx.beginPath();
ctx.arc(cx, cy, r, 0, 2*Math.PI, !1);
ctx.closePath();
};
circle(this.ctx, xy[0], xy[1], 1.5 * defaultSettings.handleThickness);
this.ctx.fill();
circle(this.ctx, xy[2], xy[3], 1.5 * defaultSettings.handleThickness);
this.ctx.fill();
}
circle(this.ctx, xy[0], xy[1], 1.5 * defaultSettings.handleThickness);
this.ctx.fill();
circle(this.ctx, xy[2], xy[3], 1.5 * defaultSettings.handleThickness);
this.ctx.fill();
// Draw bezier curve
this.ctx.beginPath();
this.ctx.lineWidth = defaultSettings.bezierThickness;
@ -197,18 +202,20 @@ BezierCanvas.prototype = {
* Emits "updated" events whenever the curve is changed. Along with the event is
* sent a CubicBezier object
*/
function CubicBezierWidget(parent, coordinates=PREDEFINED["ease-in-out"]) {
function CubicBezierWidget(parent, coordinates=PRESETS["ease-in"]["ease-in-sine"]) {
EventEmitter.decorate(this);
this.parent = parent;
let {curve, p1, p2} = this._initMarkup();
this.curve = curve;
this.curveBoundingBox = curve.getBoundingClientRect();
this.curve = curve;
this.p1 = p1;
this.p2 = p2;
// Create and plot the bezier curve
this.bezierCanvas = new BezierCanvas(this.curve,
new CubicBezier(coordinates), [.25, 0]);
new CubicBezier(coordinates), [0.30, 0]);
this.bezierCanvas.plot();
// Place the control points
@ -221,12 +228,15 @@ function CubicBezierWidget(parent, coordinates=PREDEFINED["ease-in-out"]) {
this._onPointMouseDown = this._onPointMouseDown.bind(this);
this._onPointKeyDown = this._onPointKeyDown.bind(this);
this._onCurveClick = this._onCurveClick.bind(this);
this._initEvents();
this._onNewCoordinates = this._onNewCoordinates.bind(this);
// Add preset preview menu
this.presets = new CubicBezierPresetWidget(parent);
// Add the timing function previewer
this.timingPreview = new TimingFunctionPreviewWidget(parent);
EventEmitter.decorate(this);
this._initEvents();
}
exports.CubicBezierWidget = CubicBezierWidget;
@ -235,6 +245,9 @@ CubicBezierWidget.prototype = {
_initMarkup: function() {
let doc = this.parent.ownerDocument;
let wrap = doc.createElement("div");
wrap.className = "display-wrap";
let plane = doc.createElement("div");
plane.className = "coordinate-plane";
@ -249,22 +262,24 @@ CubicBezierWidget.prototype = {
plane.appendChild(p2);
let curve = doc.createElement("canvas");
curve.setAttribute("height", "400");
curve.setAttribute("width", "200");
curve.setAttribute("width", 150);
curve.setAttribute("height", 370);
curve.id = "curve";
plane.appendChild(curve);
this.parent.appendChild(plane);
plane.appendChild(curve);
wrap.appendChild(plane);
this.parent.appendChild(wrap);
return {
p1: p1,
p2: p2,
curve: curve
}
};
},
_removeMarkup: function() {
this.parent.ownerDocument.querySelector(".coordinate-plane").remove();
this.parent.ownerDocument.querySelector(".display-wrap").remove();
},
_initEvents: function() {
@ -275,6 +290,8 @@ CubicBezierWidget.prototype = {
this.p2.addEventListener("keydown", this._onPointKeyDown);
this.curve.addEventListener("click", this._onCurveClick);
this.presets.on("new-coordinates", this._onNewCoordinates);
},
_removeEvents: function() {
@ -285,6 +302,8 @@ CubicBezierWidget.prototype = {
this.p2.removeEventListener("keydown", this._onPointKeyDown);
this.curve.removeEventListener("click", this._onCurveClick);
this.presets.off("new-coordinates", this._onNewCoordinates);
},
_onPointMouseDown: function(event) {
@ -317,7 +336,7 @@ CubicBezierWidget.prototype = {
doc.onmouseup = function () {
point.focus();
doc.onmousemove = doc.onmouseup = null;
}
};
},
_onPointKeyDown: function(event) {
@ -344,6 +363,8 @@ CubicBezierWidget.prototype = {
},
_onCurveClick: function(event) {
this.curveBoundingBox = this.curve.getBoundingClientRect();
let left = this.curveBoundingBox.left;
let top = this.curveBoundingBox.top;
let x = event.pageX - left;
@ -362,14 +383,19 @@ CubicBezierWidget.prototype = {
this._updateFromPoints();
},
_onNewCoordinates: function(event, coordinates) {
this.coordinates = coordinates;
},
/**
* Get the current point coordinates and redraw the curve to match
*/
_updateFromPoints: function() {
// Get the new coordinates from the point's offsets
let coordinates = this.bezierCanvas.offsetsToCoordinates(this.p1)
let coordinates = this.bezierCanvas.offsetsToCoordinates(this.p1);
coordinates = coordinates.concat(this.bezierCanvas.offsetsToCoordinates(this.p2));
this.presets.refreshMenu(coordinates);
this._redraw(coordinates);
},
@ -391,7 +417,7 @@ CubicBezierWidget.prototype = {
* @param {Array} coordinates
*/
set coordinates(coordinates) {
this._redraw(coordinates)
this._redraw(coordinates);
// Move the points
let offsets = this.bezierCanvas.offsets;
@ -420,6 +446,7 @@ CubicBezierWidget.prototype = {
coordinates = value.replace(/cubic-bezier|\(|\)/g, "").split(",").map(parseFloat);
}
this.presets.refreshMenu(coordinates);
this.coordinates = coordinates;
},
@ -428,11 +455,262 @@ CubicBezierWidget.prototype = {
this._removeMarkup();
this.timingPreview.destroy();
this.presets.destroy();
this.curve = this.p1 = this.p2 = null;
}
};
/**
* CubicBezierPreset widget.
* Builds a menu of presets from CubicBezierPresets
* @param {DOMNode} parent The container where the preset panel should be created
*
* Emits "new-coordinate" event along with the coordinates
* whenever a preset is selected.
*/
function CubicBezierPresetWidget(parent) {
this.parent = parent;
let {presetPane, presets, categories} = this._initMarkup();
this.presetPane = presetPane;
this.presets = presets;
this.categories = categories;
this._activeCategory = null;
this._activePresetList = null;
this._activePreset = null;
this._onCategoryClick = this._onCategoryClick.bind(this);
this._onPresetClick = this._onPresetClick.bind(this);
EventEmitter.decorate(this);
this._initEvents();
}
exports.CubicBezierPresetWidget = CubicBezierPresetWidget;
CubicBezierPresetWidget.prototype = {
/*
* Constructs a list of all preset categories and a list
* of presets for each category.
*
* High level markup:
* div .preset-pane
* div .preset-categories
* div .category
* div .category
* ...
* div .preset-container
* div .presetList
* div .preset
* ...
* div .presetList
* div .preset
* ...
*/
_initMarkup: function() {
let doc = this.parent.ownerDocument;
let presetPane = doc.createElement("div");
presetPane.className = "preset-pane";
let categoryList = doc.createElement("div");
categoryList.id = "preset-categories";
let presetContainer = doc.createElement("div");
presetContainer.id = "preset-container";
Object.keys(PRESETS).forEach(categoryLabel => {
let category = this._createCategory(categoryLabel);
categoryList.appendChild(category);
let presetList = this._createPresetList(categoryLabel);
presetContainer.appendChild(presetList);
});
presetPane.appendChild(categoryList);
presetPane.appendChild(presetContainer);
this.parent.appendChild(presetPane);
let allCategories = presetPane.querySelectorAll(".category");
let allPresets = presetPane.querySelectorAll(".preset");
return {
presetPane: presetPane,
presets: allPresets,
categories: allCategories
};
},
_createCategory: function(categoryLabel) {
let doc = this.parent.ownerDocument;
let category = doc.createElement("div");
category.id = categoryLabel;
category.classList.add("category");
let categoryDisplayLabel = this._normalizeCategoryLabel(categoryLabel);
category.textContent = categoryDisplayLabel;
return category;
},
_normalizeCategoryLabel: function(categoryLabel) {
return categoryLabel.replace("/-/g", " ");
},
_createPresetList: function(categoryLabel) {
let doc = this.parent.ownerDocument;
let presetList = doc.createElement("div");
presetList.id = "preset-category-" + categoryLabel;
presetList.classList.add("preset-list");
Object.keys(PRESETS[categoryLabel]).forEach(presetLabel => {
let preset = this._createPreset(categoryLabel, presetLabel);
presetList.appendChild(preset);
});
return presetList;
},
_createPreset: function(categoryLabel, presetLabel) {
let doc = this.parent.ownerDocument;
let preset = doc.createElement("div");
preset.classList.add("preset");
preset.id = presetLabel;
preset.coordinates = PRESETS[categoryLabel][presetLabel];
// Create preset preview
let curve = doc.createElement("canvas");
let bezier = new CubicBezier(preset.coordinates);
curve.setAttribute("height", 55);
curve.setAttribute("width", 55);
preset.bezierCanvas = new BezierCanvas(curve, bezier, [0.15, 0]);
preset.bezierCanvas.plot({
drawHandles: false,
bezierThickness: 0.025
});
preset.appendChild(curve);
// Create preset label
let presetLabelElem = doc.createElement("p");
let presetDisplayLabel = this._normalizePresetLabel(categoryLabel, presetLabel);
presetLabelElem.textContent = presetDisplayLabel;
preset.appendChild(presetLabelElem);
return preset;
},
_normalizePresetLabel: function(categoryLabel, presetLabel) {
return presetLabel.replace(categoryLabel + "-", "").replace("/-/g", " ");
},
_initEvents: function() {
for (let category of this.categories) {
category.addEventListener("click", this._onCategoryClick);
}
for (let preset of this.presets) {
preset.addEventListener("click", this._onPresetClick);
}
},
_removeEvents: function() {
for (let category of this.categories) {
category.removeEventListener("click", this._onCategoryClick);
}
for (let preset of this.presets) {
preset.removeEventListener("click", this._onPresetClick);
}
},
_onPresetClick: function(event) {
this.emit("new-coordinates", event.currentTarget.coordinates);
this.activePreset = event.currentTarget;
},
_onCategoryClick: function(event) {
this.activeCategory = event.target;
},
_setActivePresetList: function(presetListId) {
let presetList = this.presetPane.querySelector("#" + presetListId);
swapClassName("active-preset-list", this._activePresetList, presetList);
this._activePresetList = presetList;
},
set activeCategory(category) {
swapClassName("active-category", this._activeCategory, category);
this._activeCategory = category;
this._setActivePresetList("preset-category-" + category.id);
},
get activeCategory() {
return this._activeCategory;
},
set activePreset(preset) {
swapClassName("active-preset", this._activePreset, preset);
this._activePreset = preset;
},
get activePreset() {
return this._activePreset;
},
/**
* Called by CubicBezierWidget onload and when
* the curve is modified via the canvas.
* Attempts to match the new user setting with an
* existing preset.
* @param {Array} coordinates new coords [i, j, k, l]
*/
refreshMenu: function(coordinates) {
// If we cannot find a matching preset, keep
// menu on last known preset category.
let category = this._activeCategory;
// If we cannot find a matching preset
// deselect any selected preset.
let preset = null;
// If a category has never been viewed before
// show the default category.
if (!category) {
category = this.parent.querySelector("#" + DEFAULT_PRESET_CATEGORY);
}
// If the new coordinates do match a preset,
// set its category and preset button as active.
Object.keys(PRESETS).forEach(categoryLabel => {
Object.keys(PRESETS[categoryLabel]).forEach(presetLabel => {
if (coordsAreEqual(PRESETS[categoryLabel][presetLabel], coordinates)) {
category = this.parent.querySelector("#" + categoryLabel);
preset = this.parent.querySelector("#" + presetLabel);
}
});
});
this.activeCategory = category;
this.activePreset = preset;
},
destroy: function() {
this._removeEvents();
this.parent.querySelector(".preset-pane").remove();
}
};
/**
* The TimingFunctionPreviewWidget animates a dot on a scale with a given
* timing-function
@ -554,3 +832,29 @@ function isValidTimingFunction(value) {
return false;
}
/**
* Removes a class from a node and adds it to another.
* @param {String} className the class to swap
* @param {DOMNode} from the node to remove the class from
* @param {DOMNode} to the node to add the class to
*/
function swapClassName(className, from, to) {
if (from !== null) {
from.classList.remove(className);
}
if (to !== null) {
to.classList.add(className);
}
}
/**
* Compares two arrays of coordinates [i, j, k, l]
* @param {Array} c1 first coordinate array to compare
* @param {Array} c2 second coordinate array to compare
* @return {Boolean}
*/
function coordsAreEqual(c1, c2) {
return c1.reduce((prev, curr, index) => prev && (curr === c2[index]), true);
}

Просмотреть файл

@ -795,8 +795,8 @@ Tooltip.prototype = {
// Create an iframe to host the cubic-bezier widget
let iframe = this.doc.createElementNS(XHTML_NS, "iframe");
iframe.setAttribute("transparent", true);
iframe.setAttribute("width", "200");
iframe.setAttribute("height", "415");
iframe.setAttribute("width", "410");
iframe.setAttribute("height", "360");
iframe.setAttribute("flex", "1");
iframe.setAttribute("class", "devtools-tooltip-iframe");

Просмотреть файл

@ -8,14 +8,15 @@
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<link rel="stylesheet" href="chrome://browser/skin/devtools/common.css" type="text/css"/>
<link rel="stylesheet" href="chrome://browser/content/devtools/cubic-bezier.css" ype="text/css"/>
<link rel="stylesheet" href="chrome://browser/content/devtools/cubic-bezier.css" type="text/css"/>
<script type="application/javascript;version=1.8" src="theme-switching.js"/>
<style>
body {
html, body {
margin: 0;
padding: 0;
width: 200px;
height: 415px;
overflow: hidden;
width: 410px;
height: 370px;
}
</style>
</head>

Просмотреть файл

@ -5,32 +5,28 @@
/* Based on Lea Verou www.cubic-bezier.com
See https://github.com/LeaVerou/cubic-bezier */
#container {
display: flex;
width: 410px;
height: 370px;
flex-direction: row-reverse;
overflow: hidden;
}
.display-wrap {
width: 50%;
height: 100%;
text-align: center;
overflow: hidden;
}
/* Coordinate Plane */
.coordinate-plane {
position: absolute;
line-height: 0;
height: 400px;
width: 200px;
}
.coordinate-plane:before,
.coordinate-plane:after {
position: absolute;
bottom: 25%;
left: 0;
width: 100%;
}
.coordinate-plane:before {
content: "";
border-bottom: 2px solid;
transform: rotate(-90deg) translateY(2px);
transform-origin: bottom left;
}
.coordinate-plane:after {
content: "";
border-top: 2px solid;
margin-bottom: -2px;
width: 150px;
height: 370px;
margin: 0 auto;
position: relative;
}
.theme-dark .coordinate-plane:before,
@ -50,45 +46,41 @@
outline: none;
border-radius: 5px;
padding: 0;
cursor: pointer;
}
#P1x, #P1y {
color: #f08;
}
#P2x, #P2y {
color: #0ab;
}
canvas#curve {
background:
linear-gradient(-45deg, transparent 49.7%, rgba(0,0,0,.2) 49.7%, rgba(0,0,0,.2) 50.3%, transparent 50.3%) center no-repeat,
repeating-linear-gradient(transparent, #eee 0, #eee .5%, transparent .5%, transparent 10%) no-repeat,
repeating-linear-gradient(-90deg, transparent, #eee 0, #eee .5%, transparent .5%, transparent 10%) no-repeat;
background-size: 100% 50%, 100% 50%, 100% 50%;
background-position: 25%, 0, 0;
.display-wrap {
background: repeating-linear-gradient(0deg, transparent, rgba(0, 0, 0, 0.05) 0, rgba(0, 0, 0, 0.05) 1px, transparent 1px, transparent 15px) no-repeat, repeating-linear-gradient(90deg, transparent, rgba(0, 0, 0, 0.05) 0, rgba(0, 0, 0, 0.05) 1px, transparent 1px, transparent 15px) no-repeat;
background-size: 100% 100%, 100% 100%;
background-position: -2px 5px, -2px 5px;
-moz-user-select: none;
}
.theme-dark canvas#curve {
background:
linear-gradient(-45deg, transparent 49.7%, #eee 49.7%, #eee 50.3%, transparent 50.3%) center no-repeat,
repeating-linear-gradient(transparent, rgba(0,0,0,.2) 0, rgba(0,0,0,.2) .5%, transparent .5%, transparent 10%) no-repeat,
repeating-linear-gradient(-90deg, transparent, rgba(0,0,0,.2) 0, rgba(0,0,0,.2) .5%, transparent .5%, transparent 10%) no-repeat;
.theme-dark .display-wrap {
background: repeating-linear-gradient(0deg, transparent, rgba(0, 0, 0, 0.2) 0, rgba(0, 0, 0, 0.2) 1px, transparent 1px, transparent 15px) no-repeat, repeating-linear-gradient(90deg, transparent, rgba(0, 0, 0, 0.2) 0, rgba(0, 0, 0, 0.2) 1px, transparent 1px, transparent 15px) no-repeat;
background-size: 100% 100%, 100% 100%;
background-position: -2px 5px, -2px 5px;
background-size: 100% 50%, 100% 50%, 100% 50%;
background-position: 25%, 0, 0;
-moz-user-select: none;
}
canvas#curve {
background: linear-gradient(-45deg, transparent 49.7%, rgba(0,0,0,.2) 49.7%, rgba(0,0,0,.2) 50.3%, transparent 50.3%) center no-repeat;
background-size: 100% 100%;
background-position: 0 0;
}
/* Timing function preview widget */
.theme-dark canvas#curve {
background: linear-gradient(-45deg, transparent 49.7%, #eee 49.7%, #eee 50.3%, transparent 50.3%) center no-repeat;
}
/* Timing Function Preview Widget */
.timing-function-preview {
position: absolute;
top: 400px;
bottom: 20px;
right: 27px;
width: 150px;
}
.timing-function-preview .scale {
@ -97,7 +89,7 @@ canvas#curve {
left: 0;
z-index: 1;
width: 200px;
width: 150px;
height: 1px;
background: #ccc;
@ -128,10 +120,10 @@ canvas#curve {
left: -7px;
}
33% {
left: 193px;
left: 143px;
}
50% {
left: 193px;
left: 143px;
}
83% {
left: -7px;
@ -140,3 +132,109 @@ canvas#curve {
left: -7px;
}
}
/* Preset Widget */
.preset-pane {
width:50%;
height: 100%;
border-right: 1px solid var(--theme-splitter-color);
}
#preset-categories {
display: flex;
width: 94%;
border: 1px solid var(--theme-splitter-color);
border-radius: 2px;
background-color: var(--theme-toolbar-background);
margin-left: 4px;
margin-top: 3px;
}
#preset-categories .category:last-child {
border-right: none;
}
.category {
flex: 1 1 auto;
padding: 5px;
width: 33.33%;
text-align: center;
text-transform: capitalize;
border-right: 1px solid var(--theme-splitter-color);
cursor: default;
color: var(--theme-body-color);
}
.category:hover {
background-color: var(--theme-tab-toolbar-background);
}
.active-category {
background-color: var(--theme-selection-background);
color: var(--theme-selection-color);
}
.active-category:hover {
background-color: var(--theme-selection-background);
}
#preset-container {
padding: 0px;
width: 100%;
height: 331px;
overflow-y: scroll;
}
.preset-list {
display: none;
}
.active-preset-list {
display: flex;
flex-wrap: wrap;
justify-content: left;
padding-left: 4px;
padding-top: 3px;
}
.preset {
cursor: pointer;
width: 55px;
margin: 5px 11px 0px 0px;
text-transform: capitalize;
text-align: center;
}
.preset canvas {
display: block;
border: 1px solid #ccc;
border-radius: 3px;
background-color: var(--theme-body-background);
}
.theme-dark .preset canvas {
border-color: #444e58;
}
.preset p {
text-align: center;
font-size: 0.9em;
line-height: 0px;
margin: 2px 0px 0px 0p;
color: var(--theme-body-color-alt);
}
.active-preset p, .active-preset:hover p {
color: var(--theme-body-color);
}
.preset:hover canvas {
border-color: var(--theme-selection-background);
}
.active-preset canvas, .active-preset:hover canvas,
.theme-dark .active-preset canvas, .theme-dark .preset:hover canvas {
background-color: var(--theme-selection-background-semitransparent);
border-color: var(--theme-selection-background);
}