diff --git a/share/web_surfaces/builtin/mixer-demo/css/main.css b/share/web_surfaces/builtin/mixer-demo/css/main.css deleted file mode 100644 index f0543be875..0000000000 --- a/share/web_surfaces/builtin/mixer-demo/css/main.css +++ /dev/null @@ -1,134 +0,0 @@ -html { - height: 100%; -} - -body { - background: rgb(62,61,61); - color: rgb(248,248,242); - font-family: Helvetica, Arial, sans-serif; - height: 100%; - width: 100%; - position: fixed; - margin: 0; -} - -div { - box-sizing: border-box; -} - -#main { - display: flex; - flex-direction: column; - height: 100%; -} - -#surface { - flex: 1; - display: flex; - flex-direction: column; - overflow: hidden; - box-shadow: 0px 0px 10px #000; -} - -#top { - display: flex; - align-items: center; - padding: 0.25em 0.5em; - box-shadow: 0px 0px 10px #000; -} - -#top > img { - height: 24px; -} - -#top > span { - margin-left: 12px; -} - -#log { - height: 7em; - overflow: scroll; - overflow-x: hidden; - background: rgba(0,0,0,0.4); -} - -#strips { - flex: 1; - display: flex; - flex-direction: column; - align-items: center; - overflow: scroll; - overflow-x: hidden; - background: rgba(0,0,0,0.2); -} - -#log pre { - margin: 0; - font-family: Menlo, monospace; - font-size: 1em; -} - -.message-in { - color: rgb(248,248,242); -} - -.message-out { - color: rgb(172,128,255); -} - -.info { - color: rgb(99,208,230); -} - -.error { - color: rgb(249,36,114); -} - -.comp-name { - font-size: 1.5em; - font-weight: bold; -} - -.strip { - margin: 20px; - padding: 20px 40px; - background: rgba(0,0,0,0.1); - border-radius: 5px; - width: 100%; - max-width: 768px; -} - -.slider-meter { - float: right; -} - -.strip-slider { - margin-top: 20px; -} - -.plugin { - margin: 40px 0; - padding: 20px; - background: rgba(0,0,0,0.05); - border-radius: 5px; -} - -.plugin-enable { - float: right; -} - -.plugin-param { - margin: 20px 0; -} - -.plugin-param.boolean { - text-align: center; -} - -.plugin-param > .widget-switch { - margin: 10px 40px; -} - -.plugin-param.boolean { - display: inline-block; -} diff --git a/share/web_surfaces/builtin/mixer-demo/css/widget.css b/share/web_surfaces/builtin/mixer-demo/css/widget.css deleted file mode 100644 index 9681f5d651..0000000000 --- a/share/web_surfaces/builtin/mixer-demo/css/widget.css +++ /dev/null @@ -1,52 +0,0 @@ -.widget-switch { - display: block; - -webkit-appearance:none; - width: 37px; - height: 37px; - border: 3.5px solid rgb(248,248,242); - border-radius: 50%; -} - -.widget-switch:checked { - background: rgb(72,89,118); -} - -.widget-slider { - display: block; - -webkit-appearance: none; - height: 37px; - width: 100%; - background: transparent; -} - -.widget-slider::-webkit-slider-runnable-track { - height: 4px; - background: rgb(248,248,242); -} - -.widget-slider::-webkit-slider-thumb { - -webkit-appearance: none; - height: 36px; - width: 36px; - margin-top: -16px; - border: 3.5px solid rgb(248,248,242); - border-radius: 50%; - background: rgb(72,89,118); -} - -/* repeat slider style for firefox */ - -.widget-slider::-moz-range-track { - height: 4px; - background: rgb(248,248,242); -} - -.widget-slider::-moz-range-thumb { - -webkit-appearance: none; - height: 36px; - width: 36px; - margin-top: -16px; - border: 3.5px solid rgb(248,248,242); - border-radius: 50%; - background: rgb(72,89,118); -} diff --git a/share/web_surfaces/builtin/mixer-demo/index.html b/share/web_surfaces/builtin/mixer-demo/index.html deleted file mode 100644 index a2262f2b3f..0000000000 --- a/share/web_surfaces/builtin/mixer-demo/index.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - - Ardour WebSockets Demo - - - - - -
-
-
- - -
-
-
-
-
- - - diff --git a/share/web_surfaces/builtin/mixer-demo/js/main.js b/share/web_surfaces/builtin/mixer-demo/js/main.js deleted file mode 100644 index 83ae67c829..0000000000 --- a/share/web_surfaces/builtin/mixer-demo/js/main.js +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (C) 2020 Luciano Iam - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import ArdourClient from '/shared/ardour.js'; - -import { Switch, DiscreteSlider, ContinuousSlider, LogarithmicSlider, - StripPanSlider, StripGainSlider, StripMeter } from './widget.js'; - -(() => { - - const MAX_LOG_LINES = 1000; - - const ardour = new ArdourClient(); - - async function main () { - ardour.on('connected', (connected) => { - if (connected) { - log('Client connected', 'info'); - } else { - log('Client disconnected', 'error'); - } - }); - - ardour.on('message', (msg, inbound) => { - if (inbound) { - log(`↙ ${msg}`, 'message-in'); - } else { - log(`↗ ${msg}`, 'message-out'); - } - }); - - ardour.mixer.on('ready', () => { - const div = document.getElementById('strips'); - for (const strip of ardour.mixer.strips) { - createStrip(strip, div); - } - }); - - await ardour.connect(); - - const manifest = await ardour.getSurfaceManifest(); - document.getElementById('manifest').innerHTML = manifest.name.toUpperCase() - + ' v' + manifest.version + ' — ' + manifest.description; - } - - function createStrip (strip, parentDiv) { - const domId = `strip-${strip.addrId}`; - if (document.getElementById(domId) != null) { - return; - } - - const div = createElem(`
`, parentDiv); - createElem(``, div); - - // meter - const meter = new StripMeter(); - meter.el.classList.add('slider-meter'); - meter.appendTo(div); - bind(strip, 'meter', meter); - - // gain - let holder = createElem(`
`, div); - createElem(``, holder); - const gain = new StripGainSlider(); - gain.appendTo(holder); - bind(strip, 'gain', gain); - - if (!strip.isVca) { - // pan - holder = createElem(`
`, div); - createElem(``, holder); - const pan = new StripPanSlider(); - pan.appendTo(holder); - bind(strip, 'pan', pan); - } - - for (const plugin of strip.plugins) { - createStripPlugin(plugin, div); - } - } - - function createStripPlugin (plugin, parentDiv) { - const domId = `plugin-${plugin.addrId}`; - if (document.getElementById(domId) != null) { - return; - } - - const div = createElem(`
`, parentDiv); - createElem(``, div); - - const enable = new Switch(); - enable.el.classList.add('plugin-enable'); - enable.appendTo(div); - bind(plugin, 'enable', enable); - - for (const param of plugin.parameters) { - createStripPluginParam(param, div); - } - } - - function createStripPluginParam (param, parentDiv) { - const domId = `param-${param.addrId}`; - if (document.getElementById(domId) != null) { - return; - } - - let widget, cssClass; - - if (param.valueType.isBoolean) { - cssClass = 'boolean'; - widget = new Switch(); - } else if (param.valueType.isInteger) { - cssClass = 'discrete'; - widget = new DiscreteSlider(param.min, param.max); - } else if (param.valueType.isDouble) { - cssClass = 'continuous'; - if (param.isLog) { - widget = new LogarithmicSlider(param.min, param.max); - } else { - widget = new ContinuousSlider(param.min, param.max); - } - } - - const div = createElem(`
`, parentDiv); - createElem(``, div); - - widget.el.name = domId; - widget.appendTo(div); - bind(param, 'value', widget); - } - - function createElem (html, parent) { - const t = document.createElement('template'); - t.innerHTML = html; - - const elem = t.content.firstChild; - - if (parent) { - parent.appendChild(elem); - } - - return elem; - } - - function bind (component, property, widget) { - // ardour → ui - widget.value = component[property]; - component.on(property, (value) => widget.value = value); - // ui → ardour - widget.callback = (value) => component[property] = value; - } - - function log (message, className) { - const output = document.getElementById('log'); - - if (output.childElementCount > MAX_LOG_LINES) { - output.removeChild(output.childNodes[0]); - } - - const pre = document.createElement('pre'); - pre.innerHTML = message; - pre.className = className; - - output.appendChild(pre); - output.scrollTop = output.scrollHeight; - } - - main(); - -})(); diff --git a/share/web_surfaces/builtin/mixer-demo/js/widget.js b/share/web_surfaces/builtin/mixer-demo/js/widget.js deleted file mode 100644 index 74c9d75cd4..0000000000 --- a/share/web_surfaces/builtin/mixer-demo/js/widget.js +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright (C) 2020 Luciano Iam - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -export class Widget { - - constructor (html) { - const template = document.createElement('template'); - template.innerHTML = html; - this.el = template.content.firstChild; - } - - appendTo (parent) { - parent.appendChild(this.el); - } - - callback (value) { - // do nothing by default - } - -} - -export class Switch extends Widget { - - constructor () { - super (``); - this.el.addEventListener('input', (ev) => this.callback(this.value)); - } - - get value () { - return this.el.checked; - } - - set value (val) { - this.el.checked = val; - } - -} - -export class Slider extends Widget { - - constructor (min, max, step) { - const html = ``; - super(html); - this.min = min; - this.max = max; - this.el.addEventListener('input', (ev) => this.callback(this.value)); - } - - get value () { - return parseFloat(this.el.value) - } - - set value (val) { - this.el.value = val; - } - -} - -export class DiscreteSlider extends Slider { - - constructor (min, max, step) { - super(min, max, step || 1); - } - -} - -export class ContinuousSlider extends Slider { - - constructor (min, max) { - super(min, max, 0.001); - } - -} - -export class LogarithmicSlider extends ContinuousSlider { - - constructor (min, max) { - super(0, 1.0); - this.minVal = Math.log(min); - this.maxVal = Math.log(max); - this.scale = this.maxVal - this.minVal; - } - - get value () { - return Math.exp(this.minVal + this.scale * super.value); - } - - set value (val) { - this.el.value = (Math.log(val) - this.minVal) / this.scale; - } - -} - -export class StripPanSlider extends ContinuousSlider { - - constructor () { - super(-1.0, 1.0); - } - -} - -export class StripGainSlider extends ContinuousSlider { - - constructor () { - super(0, 1.0) - this.minVal = -58.0; - this.maxVal = 6.0; - this.scale = (this.maxVal - this.minVal); - } - - get value () { - return this.maxVal + Math.log10(super.value) * this.scale; - } - - set value (val) { - this.el.value = Math.pow(10.0, (val - this.maxVal) / this.scale); - } - -} - -export class StripMeter extends Widget { - - constructor () { - super(``); - } - - set value (val) { - this.el.innerHTML = val == -Infinity ? '-∞' : `${Math.round(val)} dB`; - } - -} diff --git a/share/web_surfaces/builtin/mixer-demo/manifest.xml b/share/web_surfaces/builtin/mixer-demo/manifest.xml deleted file mode 100644 index 75476a4f9e..0000000000 --- a/share/web_surfaces/builtin/mixer-demo/manifest.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/button.css b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/button.css new file mode 100644 index 0000000000..b76a40bad9 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/button.css @@ -0,0 +1,91 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +.toolkit-button { + margin: 4px; + padding: 8px 16px; + color: #002f42; + text-shadow: 0px 1px 0px rgba(255,255,255,0.6); + background-color: rgb(245,245,245); + background-image: url(../images/gradients/grey_out.png); + background-repeat: repeat-x; + background-size: 100% 200%; + background-position: 50% 0; + background-clip: padding-box; + border: 1px solid #aaa; + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; + text-decoration: none; + box-sizing: border-box; + transition: border 0.25s ease-in-out, box-shadow 0.25s ease-in-out; +} +.toolkit-button:hover { + border: 1px solid #002f42; + background-size: 100% 120%; + background-position: 50% 0; + -webkit-box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.1); + -moz-box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.1); + box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.1); +} +.toolkit-button:active { + background-image: url(../images/gradients/grey_in.png); + background-size: 100% 250%; + background-position: 50% 75%; + -webkit-box-shadow: 0px 3px 0px rgba(255,255,255,0.5), 0px -3px 0px rgba(0,0,0,0.05), 2px 0px 0px rgba(255,255,255,0.3), 0px -2px 0px rgba(0,0,0,0.025); + -moz-box-shadow: 0px 3px 0px rgba(255,255,255,0.5), 0px -3px 0px rgba(0,0,0,0.05), 2px 0px 0px rgba(255,255,255,0.3), 0px -2px 0px rgba(0,0,0,0.025); + box-shadow: 0px 3px 0px rgba(255,255,255,0.5), 0px -3px 0px rgba(0,0,0,0.05), 2px 0px 0px rgba(255,255,255,0.3), 0px -2px 0px rgba(0,0,0,0.025); + filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../images/gradients/grey_in.png',sizingMethod='scale'); +} + +.toolkit-button > .toolkit-label { + line-height: 1.1em; + text-align: center; + margin: auto; + padding: 0 3px; + transition: background 0.33s ease-in-out, color 0.33s ease-in-out; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; +} +.toolkit-button > .toolkit-icon { + color: #aaaaaa; + width: 32px; + height: 32px; + font-size: 32px; + line-height: 32px; +} + +.toolkit-button.toolkit-has-label.toolkit-vertical > .toolkit-icon { + margin-bottom: 5px; +} +.toolkit-button.toolkit-has-label.toolkit-horizontal > .toolkit-icon { + margin-right: 5px; +} + +.toolkit-button.toolkit-active { + border: 1px solid #002f42; +} +.toolkit-button.toolkit-active > .toolkit-label { + background-color: #002f42; + color: white; + text-shadow: 0px -1px 0px black; +} +.toolkit-button.toolkit-active > .toolkit-icon { + +} diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/buttonarray.css b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/buttonarray.css new file mode 100644 index 0000000000..39bde00ed0 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/buttonarray.css @@ -0,0 +1,103 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +.toolkit-buttonarray { + height: 50px; +} + +.toolkit-buttonarray.toolkit-vertical { + text-align: left; +} +.toolkit-buttonarray.toolkit-vertical > .toolkit-previous { + margin-bottom: 5px; +} +.toolkit-buttonarray.toolkit-vertical > .toolkit-next { + margin-top: 5px; +} +.toolkit-buttonarray.toolkit-horizontal > .toolkit-previous { + width: 40px; + padding-left: 4px; + padding-right: 4px; + margin-right: 5px; +} +.toolkit-buttonarray.toolkit-horizontal > .toolkit-next { + width: 40px; + padding-left: 4px; + padding-right: 4px; + margin-left: 5px; +} + +.toolkit-buttonarray > .toolkit-clip { +s -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; + border: 1px solid #aaa; + transition: left 0.5s ease-in-out, top 0.5s ease-in-out, right 0.5s ease-in-out, bottom 0.5s ease-in-out; + background-color: rgb(245,245,245); + background-image: url(../images/gradients/grey_out.png); + background-repeat: repeat-x; + background-size: 100% 200%; + background-position: 50% 0; +} +.toolkit-buttonarray.toolkit-over.toolkit-vertical > .toolkit-clip { + background-color: rgb(245,245,245); + background-image: url(../images/gradients/grey_out.png); + background-repeat: repeat-x; + background-size: 100% 200%; + background-position: 50% 0; +} + +.toolkit-buttonarray > .toolkit-clip > .toolkit-container { + transition: left 0.5s ease-in-out, top 0.5s ease-in-out; +} + +.toolkit-buttonarray.toolkit-horizontal > .toolkit-clip > .toolkit-container { + +} +.toolkit-buttonarray.toolkit-vertical > .toolkit-clip > .toolkit-container { + +} + +.toolkit-buttonarray > .toolkit-clip > .toolkit-container > .toolkit-button { + margin: 0; + border: none; + -webkit-border-radius: 0px; + -moz-border-radius: 0px; + border-radius: 0px; +} +.toolkit-buttonarray > .toolkit-clip > .toolkit-container > .toolkit-button > .toolkit-label { + white-space: nowrap; + width: 100%; + box-sizing: border-box; +} +.toolkit-buttonarray.toolkit-horizontal > .toolkit-clip > .toolkit-container > .toolkit-button { + height: 100%; +} +.toolkit-buttonarray.toolkit-vertical > .toolkit-clip > .toolkit-container > .toolkit-button { + display: flex; + width: 100%; + background: none; +} +.toolkit-buttonarray.toolkit-vertical > .toolkit-clip > .toolkit-container > .toolkit-button:hover, +.toolkit-buttonarray.toolkit-vertical > .toolkit-clip > .toolkit-container > .toolkit-button:active + { + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; + background: rgba(0,0,0,0.05); +} diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/chart.css b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/chart.css new file mode 100644 index 0000000000..dd7806b536 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/chart.css @@ -0,0 +1,67 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +.toolkit-chart > svg { + background-color: rgb(0, 56, 103); + background-image: url(../images/chart/background.png); + background-repeat: repeat-x; + background-size: 100% 100%; + background-position: 50% 50%; + background-clip: padding-box; + filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../images/chart/background.png',sizingMethod='scale'); + -webkit-box-shadow: inset 0px 4px 12px 0px rgba(0, 0, 0, 0.9); + -moz-box-shadow: inset 0px 4px 12px 0px rgba(0, 0, 0, 0.9); + box-shadow: inset 0px 4px 12px 0px rgba(0, 0, 0, 0.9); + border-top: 3px solid #ddd; + border-bottom: 3px solid #fff; + border-left: 2px solid #e8e8e8; + border-right: 2px solid #f3f3f3; +} + +.toolkit-chart { +} + +.toolkit-chart > svg > .toolkit-background { + fill: rgba(0, 0, 0, 0.2); +} +.toolkit-chart > svg > .toolkit-background.toolkit-hover { + fill: rgba(0, 0, 0, 0.5); +} +.toolkit-chart > svg > .toolkit-key { + margin: 8px; + padding: 8px; + fill: white; + opacity: 0.8; +} +.toolkit-chart > svg > .toolkit-key.toolkit-hover { + opacity: 1; +} +.toolkit-chart > svg > .toolkit-key > text > .toolkit-label { + line-height: 1.2em; /* sets the distance between key entries */ + margin: 5px; /* sets the distance between labels and example rects */ +} +.toolkit-chart > svg > .toolkit-key > .toolkit-rect { + +} + +.toolkit-chart > svg > .toolkit-title { + fill: rgba(255,255,255,0.2); + margin: 8px 26px; + font-weight: bold; + font-size: 2em; +} diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/circular.css b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/circular.css new file mode 100644 index 0000000000..dc32918cc8 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/circular.css @@ -0,0 +1,62 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +.toolkit-circular { + fill: #101010; + stroke: none; + stroke-width: 0; +} + +.toolkit-circular > .toolkit-base { + /*opacity: 0.2;*/ +} +.toolkit-circular.toolkit-warn > .toolkit-base { + opacity: 0.5; +} + +.toolkit-circular > .toolkit-value { + +} +.toolkit-circular.toolkit-warn > .toolkit-value { + opacity: 1; + fill: white; +} + +.toolkit-circular > .toolkit-hand { + fill: #c00; +} + +.toolkit-circular > .toolkit-dots { + +} +.toolkit-circular > .toolkit-dots > .toolkit-dot { +} + +.toolkit-circular > .toolkit-markers { + +} +.toolkit-circular > .toolkit-markers > .toolkit-marker { + fill: #c00; +} + +.toolkit-circular > .toolkit-labels { + +} +.toolkit-circular > .toolkit-labels > .toolkit-label { + font-size: 0.7em; +} diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/clock.css b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/clock.css new file mode 100644 index 0000000000..623a554ba4 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/clock.css @@ -0,0 +1,44 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +.toolkit-widget.toolkit-clock > svg { + -webkit-border-radius: 100%; + -moz-border-radius: 100%; + border-radius: 100%; + -webkit-box-shadow: 0px 1px 1px 0px rgba(0, 0, 0, 0.2); + -moz-box-shadow: 0px 1px 1px 0px rgba(0, 0, 0, 0.2); + box-shadow: 0px 1px 1px 0px rgba(0, 0, 0, 0.2); + fill: rgb(0,42,66); +} + +.toolkit-widget.toolkit-clock > svg > .toolkit-circular { + fill: rgb(0,42,66); +} + +.toolkit-widget.toolkit-clock > svg > .toolkit-circular > .toolkit-value { + +} + +.toolkit-widget.toolkit-clock > svg > .toolkit-circular > .toolkit-base { + fill: white; + opacity: 0.5; +} + +.toolkit-widget.toolkit-clock > svg > .toolkit-label { + margin: 5px; +} diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/colorpicker.css b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/colorpicker.css new file mode 100644 index 0000000000..72a3957b9e --- /dev/null +++ b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/colorpicker.css @@ -0,0 +1,52 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + + .toolkit-color-picker { + +} + +.toolkit-color-picker > .toolkit-canvas { +} +.toolkit-color-picker > .toolkit-canvas > .toolkit-grayscale { +} +.toolkit-color-picker > .toolkit-canvas > .toolkit-indicator { + border: 0.5px solid; + -webkit-box-shadow: 0 3px 8px rgba(0, 0, 0, 0.5); + -moz-box-shadow: 0 3px 8px rgba(0, 0, 0, 0.5); + -ms-box-shadow: 0 3px 8px rgba(0, 0, 0, 0.5); +} + + +.toolkit-color-picker > .toolkit-hex { + border: 0px solid black; +} + +.toolkit-color-picker > .toolkit-hue { +} +.toolkit-color-picker >.toolkit-saturation { +} +.toolkit-color-picker > .toolkit-lightness { +} + +.toolkit-color-picker > .toolkit-red { +} +.toolkit-color-picker > .toolkit-green { +} +.toolkit-color-picker > .toolkit-blue { +} diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/colorpickerdialog.css b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/colorpickerdialog.css new file mode 100644 index 0000000000..ff478b40db --- /dev/null +++ b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/colorpickerdialog.css @@ -0,0 +1,22 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +.toolkit-color-picker-dialog { + +} diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/container.css b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/container.css new file mode 100644 index 0000000000..43873665fa --- /dev/null +++ b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/container.css @@ -0,0 +1,21 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +.toolkit-container { + +} diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/crossover.css b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/crossover.css new file mode 100644 index 0000000000..c7c3babfa4 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/crossover.css @@ -0,0 +1,21 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +.toolkit-crossover { + +} diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/dialog.css b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/dialog.css new file mode 100644 index 0000000000..0952b46d71 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/dialog.css @@ -0,0 +1,10 @@ +.toolkit-dialog { + border: 1px solid #fff; + background: #eee; + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; + -webkit-box-shadow: 0px 2px 9px 0px rgba(0, 0, 0, 0.75); + -moz-box-shadow: 0px 2px 9px 0px rgba(0, 0, 0, 0.75); + box-shadow: 0px 2px 9px 0px rgba(0, 0, 0, 0.75); +} diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/dynamics.css b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/dynamics.css new file mode 100644 index 0000000000..0c57e931c6 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/dynamics.css @@ -0,0 +1,41 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +.toolkit-dynamics { +} + +.toolkit-dynamics > svg { + position:absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + +.toolkit-dynamics > svg > .toolkit-grid > .toolkit-grid-line.toolkit-highlight { + stroke: rgba(255,0,0,0.66); +} +.toolkit-dynamics > svg > .toolkit-grid > .toolkit-grid-label.toolkit-highlight { + fill: rgb(255,255,255); + font-weight: bold; +} + +.toolkit-dynamics > svg > .toolkit-graphs > .toolkit-steady { + stroke: rgba(255,255,255,0.5); + stroke-dasharray: 1.41421,1.41421; + stroke-width: 1px; +} diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/expander.css b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/expander.css new file mode 100644 index 0000000000..f011612c58 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/expander.css @@ -0,0 +1,68 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +/* POPUP */ +.toolkit-expander.toolkit-popup { + background: rgba(216, 216, 216, 0.9); +} +.toolkit-expander.toolkit-popup.toolkit-expanded > .toolkit-toggle-expand { + width: 42px; + height: 42px; +} +.toolkit-expander.toolkit-popup.toolkit-expanded > .toolkit-toggle-expand::after, +.toolkit-expander.toolkit-popup.toolkit-expanded > .toolkit-toggle-expand::before { + content: ""; + display: block; + position: absolute; + height: 4px; + width: 32px; + top: 19px; + left: 5px; + transform: rotate(45deg); + background: #003050; +} +.toolkit-expander.toolkit-popup.toolkit-expanded > .toolkit-toggle-expand::before { + transform: rotate(135deg); +} + +/* POPUP INPLACE */ + +.toolkit-expander.toolkit-popup-inplace { + background: rgba(216, 216, 216, 0.9); +} + +.toolkit-expander.toolkit-popup-inplace.toolkit-expanded > .toolkit-toggle-expand { + width: 42px; + height: 42px; +} +.toolkit-expander.toolkit-popup-inplace.toolkit-expanded > .toolkit-toggle-expand::after, +.toolkit-expander.toolkit-popup-inplace.toolkit-expanded > .toolkit-toggle-expand::before { + content: ""; + display: block; + position: absolute; + height: 4px; + width: 32px; + top: 19px; + left: 5px; + transform: rotate(45deg); + background: #003050; +} +.toolkit-expander.toolkit-popup.toolkit-expanded > .toolkit-toggle-expand::before { + transform: rotate(135deg); +} diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/fader.css b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/fader.css new file mode 100644 index 0000000000..b3d36cc621 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/fader.css @@ -0,0 +1,140 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +.toolkit-fader { + margin: 4px; + padding: 0px; + background-repeat: no-repeat; +} + +.toolkit-fader.toolkit-horizontal { + grid-column-gap: 4px; + grid-row-gap: 2px; +} +.toolkit-fader.toolkit-vertical { + grid-column-gap: 2px; + grid-row-gap: 4px; +} + +/* TRACK */ + + +.toolkit-fader > .toolkit-track { + background-color: #303030; + border-radius: 5px; +} + +.toolkit-fader.toolkit-vertical > .toolkit-track { + width: 46px; +} +.toolkit-fader.toolkit-horizontal > .toolkit-track { + height: 46px; +} + +.toolkit-fader.toolkit-top > .toolkit-track { + align-self: end; +} +.toolkit-fader.toolkit-bottom > .toolkit-track { + align-self: start; +} +.toolkit-fader.toolkit-left > .toolkit-track { + justify-self: end; +} +.toolkit-fader.toolkit-right > .toolkit-track { + justify-self: start; +} + +/* HANDLE */ + +.toolkit-fader > .toolkit-track > .toolkit-handle { + cursor: pointer; + cursor: hand; + margin: 1px; + box-shadow: 1px 3px 8px rgba(0, 0, 0, 0.5); + border-radius: 4px; +} +.toolkit-fader.toolkit-vertical > .toolkit-track > .toolkit-handle { + width: 44px; + height: 80px; + background: url(../images/fader/handle_vertical.png) no-repeat; +} +.toolkit-fader.toolkit-vertical.toolkit-warn > .toolkit-track > .toolkit-handle { + width: 44px; + height: 80px; + background: url(../images/fader/handle_vertical_warn.png) no-repeat; +} +.toolkit-fader.toolkit-horizontal > .toolkit-track > .toolkit-handle { + height: 44px; + width: 80px; + background: url(../images/fader/handle_horizontal.png) no-repeat; +} +.toolkit-fader.toolkit-horizontal.toolkit-warn > .toolkit-track > .toolkit-handle { + height: 44px; + width: 80px; + background: url(../images/fader/handle_horizontal_warn.png) no-repeat; +} + + +/* SCALE */ + +.toolkit-fader.toolkit-horizontal > .toolkit-scale { + margin: 0 40px; +} +.toolkit-fader.toolkit-vertical > .toolkit-scale { + margin: 40px 0; +} + +.toolkit-fader.toolkit-top > .toolkit-scale { + align-self: start; +} +.toolkit-fader.toolkit-bottom > .toolkit-scale { + align-self: end; +} +.toolkit-fader.toolkit-left > .toolkit-scale { + justify-self: start; +} +.toolkit-fader.toolkit-right > .toolkit-scale { + justify-self: end; +} + +/* LABEL */ + +.toolkit-fader > .toolkit-label { + line-height: 23px; + height: 23px; + text-align: center; +} + + +/* VALUE */ + +.toolkit-fader > .toolkit-value, +.toolkit-fader > .toolkit-value > .toolkit-input { + line-height: 23px; + height: 23px; + font-size: 14px; +} +.toolkit-fader > .toolkit-value > .toolkit-input { + background: transparent; +} +.toolkit-fader > .toolkit-value.toolkit-active { + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5); +} +.toolkit-fader > .toolkit-value.toolkit-active > .toolkit-input { + background: #002f42; +} diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/frame.css b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/frame.css new file mode 100644 index 0000000000..b0e890744b --- /dev/null +++ b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/frame.css @@ -0,0 +1,28 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +.toolkit-frame { + padding: 8px; + margin-top: 8px; +} +.toolkit-frame > .toolkit-frame-label { + font-weight: bold; + position: absolute; + top: -8px; + left: 8px; +} diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/frequencyresponse.css b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/frequencyresponse.css new file mode 100644 index 0000000000..614f7dc085 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/frequencyresponse.css @@ -0,0 +1,29 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +.toolkit-frequency-response { + +} + +.toolkit-frequency-response > svg > .toolkit-grid > .toolkit-grid-line.toolkit-highlight { + stroke: rgba(255,0,0,0.66); +} +.toolkit-frequency-response > svg > .toolkit-grid > .toolkit-grid-label.toolkit-highlight { + fill: rgb(255,255,255); + font-weight: bold; +} diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/gauge.css b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/gauge.css new file mode 100644 index 0000000000..ba130426e4 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/gauge.css @@ -0,0 +1,34 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +.toolkit-gauge { + margin: 4px; +} + +.toolkit-gauge > svg > .toolkit-circular { + fill: rgb(0,42,66); +} + +.toolkit-gauge > svg > .toolkit-circular > .toolkit-base { + fill: white; + opacity: 0.5; +} + +.toolkit-gauge > svg > .toolkit-title { + fill: rgb(0,42,66); +} diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/globalcursor.css b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/globalcursor.css new file mode 100644 index 0000000000..fb21dd90bb --- /dev/null +++ b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/globalcursor.css @@ -0,0 +1,106 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +body.toolkit-cursor-default, +body.toolkit-cursor-default * { + cursor:default !important; +} +body.toolkit-cursor-crosshair, +body.toolkit-cursor-crosshair * { + cursor:crosshair !important; +} +body.toolkit-cursor-pointer, +body.toolkit-cursor-pointer * { + cursor:pointer !important; +} +body.toolkit-cursor-move, +body.toolkit-cursor-move * { + cursor:move !important; +} +body.toolkit-cursor-text, +body.toolkit-cursor-text * { + cursor:text !important; +} +body.toolkit-cursor-wait, +body.toolkit-cursor-wait * { + cursor:wait !important; +} +body.toolkit-cursor-help, +body.toolkit-cursor-help * { + cursor:help !important; +} +body.toolkit-cursor-n-resize, +body.toolkit-cursor-n-resize * { + cursor:n-resize !important; +} +body.toolkit-cursor-ne-resize, +body.toolkit-cursor-ne-resize * { + cursor:ne-resize !important; +} +body.toolkit-cursor-e-resize, +body.toolkit-cursor-e-resize * { + cursor:e-resize !important; +} +body.toolkit-cursor-se-resize, +body.toolkit-cursor-se-resize * { + cursor:se-resize !important; +} +body.toolkit-cursor-s-resize, +body.toolkit-cursor-s-resize * { + cursor:s-resize !important; +} +body.toolkit-cursor-sw-resize, +body.toolkit-cursor-sw-resize * { + cursor:sw-resize !important; +} +body.toolkit-cursor-w-resize, +body.toolkit-cursor-w-resize * { + cursor:w-resize !important; +} +body.toolkit-cursor-nw-resize, +body.toolkit-cursor-nw-resize * { + cursor:nw-resize !important; +} +body.toolkit-cursor-progress, +body.toolkit-cursor-progress * { + cursor:progress !important; +} +body.toolkit-cursor-not-allowed, +body.toolkit-cursor-not-allowed * { + cursor:not-allowed !important; +} +body.toolkit-cursor-no-drop, +body.toolkit-cursor-no-drop * { + cursor:no-drop !important; +} +body.toolkit-cursor-vertical-text, +body.toolkit-cursor-vertical-text * { + cursor:vertical-text !important; +} +body.toolkit-cursor-all-scroll, +body.toolkit-cursor-all-scroll * { + cursor:all-scroll !important; +} +body.toolkit-cursor-col-resize, +body.toolkit-cursor-col-resize * { + cursor:col-resize !important; +} +body.toolkit-cursor-row-resize, +body.toolkit-cursor-row-resize * { + cursor:row-resize !important; +} diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/graph.css b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/graph.css new file mode 100644 index 0000000000..063ee8701a --- /dev/null +++ b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/graph.css @@ -0,0 +1,37 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +.toolkit-graph { + stroke: #fff; + fill: none; + stroke-width: 1.2px; + stroke-linecap: round; + stroke-linejoin: round; +} + +.toolkit-graph.toolkit-filled { + stroke-width: 1.2px; + stroke: #fff; + fill: rgba(255,255,255,0.25); +} + +.toolkit-graph.toolkit-outline { + stroke-width: 2px; + stroke: #fff; + fill: none; +} diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/grid.css b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/grid.css new file mode 100644 index 0000000000..7f6207c244 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/grid.css @@ -0,0 +1,41 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +.toolkit-grid-line { + stroke: rgba(255,255,255,0.25); + stroke-dasharray: 1,1; +} +.toolkit-grid-line.toolkit-vertical { + +} +.toolkit-grid-line.toolkit-horizontal { + +} + +.toolkit-grid-label { + fill: rgba(255,255,255,0.99); + /* ONLY USE PADDING FOR POSITIONING! */ + padding: 1px 2px; + font-size: 0.7em; +} +.toolkit-grid-label.toolkit-vertical { + +} +.toolkit-grid-label.toolkit-horizontal { + +} diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/icon.css b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/icon.css new file mode 100644 index 0000000000..70bf570a93 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/icon.css @@ -0,0 +1,26 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +.toolkit-icon { + margin: auto; + width: 32px; + height: 32px; + font-size: 32px; + line-height: 32px; +} diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/knob.css b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/knob.css new file mode 100644 index 0000000000..724e31663a --- /dev/null +++ b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/knob.css @@ -0,0 +1,46 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +.toolkit-knob { + /*background: url(../images/knob/background.svg) no-repeat 50% 50%;*/ + background-size: contain; + cursor: pointer; + width: 100px; + height: 100px; +} +.toolkit-knob > svg { + width: 100%; + height: 100%; +} +.toolkit-knob > svg > .toolkit-circular > .toolkit-hand { + fill: #002a42; +} + +.toolkit-knob > svg > .toolkit-circular > .toolkit-base { + +} +.toolkit-knob > svg > .toolkit-circular > .toolkit-value { + fill: #487c92; +} +.toolkit-knob > svg > .toolkit-circular > .toolkit-dots > .toolkit-dot { + fill: #002f42; +} +.toolkit-knob > svg > .toolkit-circular > .toolkit-labels > .toolkit-label { + font-size: 8px; + fill: #002f42; +} diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/label.css b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/label.css new file mode 100644 index 0000000000..dc6b32df24 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/label.css @@ -0,0 +1,21 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +.toolkit-label { + overflow: hidden; +} diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/levelmeter.css b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/levelmeter.css new file mode 100644 index 0000000000..094357aaa2 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/levelmeter.css @@ -0,0 +1,156 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +/* UNIVERSAL */ + +.toolkit-level-meter.toolkit-clipping > .toolkit-label { + color: #ff8000; + text-shadow: 0 0 3px white,0 0 3px white; +} + +/* CLIP */ + +.toolkit-level-meter > .toolkit-clip { + background-color: #f60 !important; + margin: 0; +} +.toolkit-level-meter.toolkit-horizontal > .toolkit-clip { + height: 32px; + width: 8px; +} + +/* PEAK */ + +.toolkit-level-meter > .toolkit-bar > .toolkit-peak { + line-height: 16px; +} + +.toolkit-level-meter.toolkit-vertical > .toolkit-bar > .toolkit-peak { + top: 0; + width: 100%; + height: 12px; + transform: translateY(-50%); +} +.toolkit-level-meter.toolkit-vertical.toolkit-left > .toolkit-bar > .toolkit-peak { + left: 0px; + text-align: left; +} +.toolkit-level-meter.toolkit-vertical.toolkit-left > .toolkit-bar > .toolkit-peak::before { + content: ""; + display: block; + position: absolute; + top: 50%; + left: 0; + height: 0; + width: 0; + border-left: 5px solid #efefef; + border-top: 3px solid transparent; + border-bottom: 3px solid transparent; + z-index: 1; + transform: translateY(-3px); +} +.toolkit-level-meter.toolkit-vertical.toolkit-right > .toolkit-bar > .toolkit-peak { + right: 0px; + text-align: right; +} +.toolkit-level-meter.toolkit-vertical.toolkit-right > .toolkit-bar > .toolkit-peak::before { + content: ""; + display: block; + position: absolute; + top: 50%; + right: 0; + height: 0; + width: 0; + border-right: 5px solid #f5f5f5; + border-top: 3px solid transparent; + border-bottom: 3px solid transparent; + z-index: 1; + transform: translateY(-3px); +} + +.toolkit-level-meter.toolkit-horizontal > .toolkit-bar > .toolkit-peak { + transform: translateX(-50%); +} +.toolkit-level-meter.toolkit-horizontal.toolkit-top > .toolkit-bar > .toolkit-peak { + top: 0px; +} +.toolkit-level-meter.toolkit-horizontal.toolkit-top > .toolkit-bar > .toolkit-peak::before { + content: ""; + display: block; + position: absolute; + left: 50%; + top: 0; + height: 0; + width: 0; + border-top: 5px solid #ddd; + border-left: 3px solid transparent; + border-right: 3px solid transparent; + z-index: 1; + transform: translateX(-3px); +} +.toolkit-level-meter.toolkit-horizontal.toolkit-bottom > .toolkit-bar > .toolkit-peak { + bottom: 0px; +} +.toolkit-level-meter.toolkit-horizontal.toolkit-bottom > .toolkit-bar > .toolkit-peak::before { + content: ""; + display: block; + position: absolute; + left: 50%; + bottom: 0; + height: 0; + width: 0; + border-bottom: 5px solid #f5f5f5; + border-right: 3px solid transparent; + border-left: 3px solid transparent; + z-index: 1; + transform: translateX(-3px); +} +/* PEAK LABEL */ + +.toolkit-level-meter > .toolkit-bar > .toolkit-peak > .toolkit-peak-label { + color: white; + font-size: 8px; + line-height: 12px; + opacity: 0.9; + overflow: hidden; +} +.toolkit-level-meter.toolkit-vertical > .toolkit-bar > .toolkit-peak .toolkit-peak-label { + text-shadow: 0 1px 2px black,0 1px 2px black; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; +} +.toolkit-level-meter.toolkit-vertical.toolkit-left > .toolkit-bar > .toolkit-peak > .toolkit-peak-label { + padding-left: 7px; +} +.toolkit-level-meter.toolkit-vertical.toolkit-right > .toolkit-bar > .toolkit-peak > .toolkit-peak-label { + padding-right: 7px; +} + +.toolkit-level-meter.toolkit-horizontal > .toolkit-bar > .toolkit-peak .toolkit-peak-label { + text-shadow: -1px 0 2px black,-1px 0 2px black; + padding: 7px; +} +.toolkit-level-meter.toolkit-horizontal.toolkit-top > .toolkit-bar > .toolkit-peak > .toolkit-peak-label { + +} +.toolkit-level-meter.toolkit-horizontal.toolkit-bottom > .toolkit-bar > .toolkit-peak > .toolkit-peak-label { + +} diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/meterbase.css b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/meterbase.css new file mode 100644 index 0000000000..f91cfc5332 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/meterbase.css @@ -0,0 +1,101 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +/* + AVAILABLE DEFAULT BACKGROUNDS + background-image: url(../images/meterbase/grid_soft.png); + background-image: url(../images/meterbase/grid_strong.png); + background-image: url(../images/meterbase/slim_soft_vertical.png); + background-image: url(../images/meterbase/slim_strong_vertical.png); + background-image: url(../images/meterbase/slim_soft_horizontal.png); + background-image: url(../images/meterbase/slim_strong_horizontal.png); + background-image: url(../images/meterbase/led_vertical.png); + background-image: url(../images/meterbase/led_horizontal.png); + background-image: url(../images/meterbase/bevel_soft_vertical.png); + background-image: url(../images/meterbase/bevel_strong_vertical.png); + background-image: url(../images/meterbase/bevel_soft_horizontal.png); + background-image: url(../images/meterbase/bevel_strong_horizontal.png); +*/ + +/* BASE */ + +.toolkit-meter-base { + /*padding: 4px; + background: #eee; + border: 1px solid #fff; + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; + -webkit-box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.2); + -moz-box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.2); + box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.2); + box-sizing: border-box; + margin: 4px;*/ + background: #139513; +} + +/* LABEL */ + +.toolkit-meter-base > .toolkit-label { + line-height: 1.2em; + height: 1.2em; + overflow: visible; + font-size: 0.8em; +} + +/* TITLE */ + +.toolkit-meter-base > .toolkit-title { + line-height: 1.2em; + height: 1.2em; + overflow: visible; + font-size: 0.8em; +} + +/* SCALE */ + +.toolkit-meter-base > .toolkit-scale { + color: #aaa; + border: 0 solid transparent; + border-width: 3px 2px; +} +.toolkit-meter-base > .toolkit-scale > .toolkit-label.toolkit-base { + color: #666; +} + +/* BAR */ + +.toolkit-meter-base > .toolkit-bar { + /*border-top: 3px solid #ddd; + border-bottom: 3px solid #fff; + border-left: 2px solid #e8e8e8; + border-right: 2px solid #f3f3f3; + background-clip: padding-box !important;*/ +} +.toolkit-meter-base.toolkit-horizontal > .toolkit-bar { + height: 32px; +} +.toolkit-meter-base.toolkit-vertical > .toolkit-bar { + width: 8px; +} + +/* MASK */ + +.toolkit-meter-base > .toolkit-bar > .toolkit-mask { + background: #303030; +} diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/multimeter.css b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/multimeter.css new file mode 100644 index 0000000000..a94bf787cb --- /dev/null +++ b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/multimeter.css @@ -0,0 +1,89 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +/* BASIC STUFF */ + +.toolkit-multi-meter { + position: relative; + background: #eee; + border: 1px solid #fff; + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; + -webkit-box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.2); + -moz-box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.2); + box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.2); + margin: 3px; + padding: 3px; + padding-top: calc(1.2em + 8px); +} + +.toolkit-multi-meter > .toolkit-title { + height: 1.2em; + line-height: 1.2em; + position: absolute; + top: 3px; + left: 3px; +} + +/* levelmeter */ + +.toolkit-multi-meter > .toolkit-level-meter { + border: none; + background: transparent; + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; + margin: 0; + padding: 0; + -webkit-border-radius: 0px; + -moz-border-radius: 0px; + border-radius: 0px; +} + +.toolkit-multi-meter.toolkit-vertical > .toolkit-level-meter > .toolkit-bar, +.toolkit-multi-meter.toolkit-vertical > .toolkit-level-meter > .toolkit-state { + border-width: 3px 1px 3px 0; + width: 20px; +} +.toolkit-multi-meter.toolkit-vertical > .toolkit-level-meter:nth-of-type(1) > .toolkit-bar, +.toolkit-multi-meter.toolkit-vertical > .toolkit-level-meter:nth-of-type(1) > .toolkit-state { + border-left-width: 2px; +} +.toolkit-multi-meter.toolkit-vertical > .toolkit-level-meter:nth-last-of-type(1) > .toolkit-bar, +.toolkit-multi-meter.toolkit-vertical > .toolkit-level-meter:nth-last-of-type(1) > .toolkit-state { + border-right-width: 2px; +} +.toolkit-multi-meter.toolkit-horizontal > .toolkit-level-meter > .toolkit-bar, +.toolkit-multi-meter.toolkit-horizontal > .toolkit-level-meter > .toolkit-state { + border-width: 1px 2px 0px 2px; + height: 20px; +} +.toolkit-multi-meter.toolkit-horizontal > .toolkit-level-meter:nth-of-type(1) > .toolkit-bar, +.toolkit-multi-meter.toolkit-horizontal > .toolkit-level-meter:nth-of-type(1) > .toolkit-state { + border-top-width: 2px; +} +.toolkit-multi-meter.toolkit-horizontal > .toolkit-level-meter:nth-last-of-type(1) > .toolkit-bar, +.toolkit-multi-meter.toolkit-horizontal > .toolkit-level-meter:nth-last-of-type(1) > .toolkit-state { + border-bottom-width: 2px; +} + +.toolkit-multi-meter.toolkit-horizontal .toolkit-level-meter > .toolkit-title { + margin-right: 4px; +} diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/notification.css b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/notification.css new file mode 100644 index 0000000000..be6e3e4d2d --- /dev/null +++ b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/notification.css @@ -0,0 +1,113 @@ +.toolkit-notification { + background: rgba(238, 238, 238, 0.9); + border: 1px solid rgba(255, 255, 255, 0.9); + margin: 4px; + padding: 4px 8px; + min-height: 32px; + + font-size: 1rem; + text-align: left; + + -webkit-box-shadow: 0px 2px 9px 0px rgba(0, 0, 0, 0.25); + -moz-box-shadow: 0px 2px 9px 0px rgba(0, 0, 0, 0.25); + box-shadow: 0px 2px 9px 0px rgba(0, 0, 0, 0.25); + + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; + + display: block; +} + +.toolkit-notification > .toolkit-button { + background: transparent !important; + border: none !important; + position: absolute; + top: 4px; + right: 4px; + width: 20px !important; + height: 20px !important; + margin: 0; + padding: 0; + box-shadow: none !important; +} +.toolkit-notification > .toolkit-button.toolkit-close .toolkit-icon { + font-size: 20px !important; + line-height: 20px !important; + width: 20px !important; + height: 20px !important; +} + +.toolkit-notification > .toolkit-icon { + position: absolute; + font-size: 32px; + line-height: 32px; + color: #002A42; + top: 8px; + left: 8px; +} + +.toolkit-notification.toolkit-has-icon { + padding-left: 56px; +} + +.toolkit-notification.toolkit-has-close { + padding-right: 48px; +} + + +.toolkit-notification { + opacity: 0; + + -webkit-animation: -webkit-fade-in 0.5s; + -moz-animation: -moz-fade-in 0.5s; + -ms-animation: -ms-fade-in 0.5s; + -o-animation: -o-fade-in 0.5s; + animation: fade-in 0.5s; + + -webkit-transform: scale(1); + -moz-transform: scale(1); + -ms-transform: scale(1); + -o-transform: scale(1); + transform: scale(1); +} + +.toolkit-notification.toolkit-show { + opacity: 1; +} +.toolkit-notification.toolkit-hiding { + opacity: 0; + + -webkit-transition: opacity 0.5s, transform 0.5s; + -moz-transition: opacity 0.5s, transform 0.5s; + -ms-transition: opacity 0.5s, transform 0.5s; + -o-transition: opacity 0.5s, transform 0.5s; + transition: opacity 0.5s, transform 0.5s; + + -webkit-transform: scale(0); + -moz-transform: scale(0); + -ms-transform: scale(0); + -o-transform: scale(0); + transform: scale(0); +} + +@-webkit-keyframes -webit-fade-in { + 0% { opacity: 0; -webit-transform: scale(1); } + 100% { opacity: 1; -webit-transform: scale(1);} +} +@-moz-keyframes -moz-fade-in { + 0% { opacity: 0; -moz-transform: scale(1); } + 100% { opacity: 1; -moz-transform: scale(1);} +} +@-ms-keyframes -ms-fade-in { + 0% { opacity: 0; -ms-transform: scale(1); } + 100% { opacity: 1; -ms-transform: scale(1);} +} +@-o-keyframes -o-fade-in { + 0% { opacity: 0; -o-transform: scale(1); } + 100% { opacity: 1; -o-transform: scale(1);} +} +@keyframes fade-in { + 0% { opacity: 0; transform: scale(1); } + 100% { opacity: 1; transform: scale(1);} +} diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/notifications.css b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/notifications.css new file mode 100644 index 0000000000..5ecfc3bb42 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/notifications.css @@ -0,0 +1,10 @@ +.toolkit-notifications { + position: fixed; + z-index: 100000000000; + bottom: 0; + right: 0; + width: 100%; + max-width: 400px; + + transform: translate3d(0, 0, 0); +} diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/pager.css b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/pager.css new file mode 100644 index 0000000000..cd5466c28b --- /dev/null +++ b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/pager.css @@ -0,0 +1,35 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +/* GENERAL */ + +.toolkit-pager { + width: 100%; + height: 100%; +} +.toolkit-pager > .toolkit-clip { + overflow: hidden; +} + +.toolkit-pager > .toolkit-clip > .toolkit-page { + opacity: 1; + overflow: auto; +} +.toolkit-container.toolkit-hide { + +} diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/responsehandle.css b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/responsehandle.css new file mode 100644 index 0000000000..57f2b27ca8 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/responsehandle.css @@ -0,0 +1,227 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +.toolkit-response-handle { + fill: rgb(255,255,255); +} +.toolkit-response-handle.toolkit-disabled { + display: none; +} +.toolkit-response-handle:hover { + +} +.toolkit-response-handle.toolkit-inactive { + +} +.toolkit-response-handle.toolkit-active { + cursor: move !important; +} +.toolkit-response-handle.toolkit-warn { + +} +.toolkit-response-handle.toolkit-circular { + +} +.toolkit-response-handle.toolkit-circular:hover { + +} +.toolkit-response-handle.toolkit-circular.toolkit-active { + +} +.toolkit-response-handle.toolkit-line { + +} +.toolkit-response-handle.toolkit-line-vertical { + +} +.toolkit-response-handle.toolkit-line-horizontal { + +} +.toolkit-response-handle.toolkit-block { + +} +.toolkit-response-handle.toolkit-block-left { + +} +.toolkit-response-handle.toolkit-block-right { + +} +.toolkit-response-handle.toolkit-block-top { + +} +.toolkit-response-handle.toolkit-block-bottom { + +} + +.toolkit-response-handle > .toolkit-z-handle { + fill: rgb(255,255,255); + opacity: 0.5; + cursor: pointer; + stroke: rgba(255,255,255, 0.5); + stroke-width: 4; +} +.toolkit-response-handle > .toolkit-z-handle:hover { + opacity: 0.75; + stroke: rgb(255,255,255); +} +.toolkit-response-handle.toolkit-active > .toolkit-z-handle { + opacity: 0.75; + cursor: move; + stroke: rgb(255,255,255); +} + +.toolkit-response-handle > .toolkit-label { + cursor: pointer; + font-size: 0.8em; +} +.toolkit-response-handle:hover > .toolkit-label { + +} +.toolkit-response-handle.toolkit-active > .toolkit-label { + cursor:move; +} +.toolkit-response-handle > .toolkit-label > tspan { + +} + +.toolkit-response-handle.toolkit-circular > .toolkit-label { + opacity: 0.5; +} +.toolkit-response-handle.toolkit-circular:hover > .toolkit-label { + opacity: 0.66; +} +.toolkit-response-handle.toolkit-circular.toolkit-active > .toolkit-label { + opacity: 0.8; +} +.toolkit-response-handle.toolkit-line > .toolkit-label { + opacity: 0.5; +} +.toolkit-response-handle.toolkit-line:hover > .toolkit-label { + opacity: 0.66; +} +.toolkit-response-handle.toolkit-line.toolkit-active > .toolkit-label { + opacity: 0.8; +} +.toolkit-response-handle.toolkit-block > .toolkit-label { + opacity: 0.5; +} +.toolkit-response-handle.toolkit-block:hover > .toolkit-label { + opacity: 0.66; +} +.toolkit-response-handle.toolkit-block.toolkit-active > .toolkit-label { + opacity: 0.8; +} + + + +.toolkit-response-handle > .toolkit-handle { + cursor:pointer; +} +.toolkit-response-handle.toolkit-active > .toolkit-handle { + cursor:move; +} +.toolkit-response-handle.toolkit-warn > .toolkit-handle { + stroke: rgba(255,255,255,0.33); + stroke-width: 20; +} +.toolkit-response-handle.toolkit-circular > .toolkit-handle { + opacity: 0.33; +} +.toolkit-response-handle.toolkit-circular:hover > .toolkit-handle { + opacity: 0.5; +} +.toolkit-response-handle.toolkit-circular.toolkit-active > .toolkit-handle { + opacity: 0.66; +} +.toolkit-response-handle.toolkit-line > .toolkit-handle { + opacity: 0.33; +} +.toolkit-response-handle.toolkit-line:hover > .toolkit-handle { + opacity: 0.5; +} +.toolkit-response-handle.toolkit-line.toolkit-active > .toolkit-handle { + opacity: 0.66; +} +.toolkit-response-handle.toolkit-block > .toolkit-handle { + opacity: 0.2; +} +.toolkit-response-handle.toolkit-block:hover > .toolkit-handle { + opacity: 0.33; +} +.toolkit-response-handle.toolkit-block.toolkit-active > .toolkit-handle { + opacity: 0.5; +} + + + +.toolkit-response-handle > .toolkit-line { + stroke-width: 1; +} +.toolkit-response-handle:hover > .toolkit-line { + +} +.toolkit-response-handle.toolkit-active > .toolkit-line { + +} +.toolkit-response-handle.toolkit-circular > .toolkit-line { + stroke: white; +/* stroke-dasharray: 1,1; */ + opacity: 0.5; +} +.toolkit-response-handle.toolkit-circular.toolkit-active > .toolkit-line { + +} + +.toolkit-response-handle.toolkit-line > .toolkit-line-1 { + stroke: #fff; + stroke-dasharray: 1,1; + opacity: 0.6; +} +.toolkit-response-handle.toolkit-line:hover > .toolkit-line-1 { + stroke-dasharray: 0; +} +.toolkit-response-handle.toolkit-line.toolkit-active > .toolkit-line-1 { + stroke-dasharray: 0; +} +.toolkit-response-handle.toolkit-line > .toolkit-line-2 { + stroke: #fff; + opacity: 0.5; +} +.toolkit-response-handle.toolkit-line.toolkit-active > .toolkit-line-2 { + +} + +.toolkit-response-handle.toolkit-block > .toolkit-line-1 { + stroke: #fff; + stroke-dasharray: 1,1; + opacity: 0.6; +} +.toolkit-response-handle.toolkit-block:hover > .toolkit-line-1 { + stroke-dasharray: 0; +} +.toolkit-response-handle.toolkit-block.toolkit-active > .toolkit-line-1 { + stroke-dasharray: 0; + opacity: 0.9; +} +.toolkit-response-handle.toolkit-block > .toolkit-line-2 { + stroke: #fff; + opacity: 0.5; +} +.toolkit-response-handle.toolkit-block.toolkit-active > .toolkit-line-2 { + +} diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/responsehandler.css b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/responsehandler.css new file mode 100644 index 0000000000..35f3903e71 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/responsehandler.css @@ -0,0 +1,27 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +.toolkit-response-handler { + +} +.toolkit-response-handler.toolkit-dragging { + cursor: move; +} +.toolkit-response-handler > .toolkit-response-handles { + +} diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/root.css b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/root.css new file mode 100644 index 0000000000..8f8d78aab1 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/root.css @@ -0,0 +1,21 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +.toolkit-root { + +} diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/scale.css b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/scale.css new file mode 100644 index 0000000000..e70686ce1d --- /dev/null +++ b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/scale.css @@ -0,0 +1,289 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +.toolkit-scale { + +} +.toolkit-scale > .toolkit-label { + font-size: 0.7em; + height: 1em; + line-height: 1em; + color: #aaa; + white-space: nowrap; + margin: 0 0 0 0; + overflow: visible; +} +.toolkit-scale > .toolkit-label.toolkit-base { + color: #f10000; + font-weight: 500; +} +.toolkit-scale > .toolkit-label.toolkit-min { + color: #666; +} +.toolkit-scale > .toolkit-label.toolkit-max { + color: #666; +} +.toolkit-scale > .toolkit-dot { + width: 1px; + height: 1px; + background: #aaa; +} +.toolkit-scale > .toolkit-dot.toolkit-base { + background: #951c26; +} +.toolkit-scale > .toolkit-dot.toolkit-min { + background: #666; +} +.toolkit-scale > .toolkit-dot.toolkit-max { + background: #666; +} +.toolkit-scale > .toolkit-dot.toolkit-marker { + background: #666; +} +/* VERTICAL */ + +.toolkit-scale.toolkit-vertical { + width: 40px; +} +.toolkit-scale.toolkit-vertical > .toolkit-label { +} +.toolkit-scale.toolkit-vertical > .toolkit-label.toolkit-base { + +} +.toolkit-scale.toolkit-vertical > .toolkit-label.toolkit-min { +} +.toolkit-scale.toolkit-vertical > .toolkit-label.toolkit-max { +} +.toolkit-scale.toolkit-vertical > .toolkit-dot { + width: 2px; +} +.toolkit-scale.toolkit-vertical > .toolkit-dot.toolkit-base { + width: 4px; +} +.toolkit-scale.toolkit-vertical > .toolkit-dot.toolkit-min { + width: 4px; +} +.toolkit-scale.toolkit-vertical > .toolkit-dot.toolkit-max { + width: 4px; +} +.toolkit-scale.toolkit-vertical > .toolkit-dot.toolkit-marker { + width: 4px; +} +/* VERTICAL sticked to LEFT */ + +.toolkit-scale.toolkit-vertical.toolkit-left { + text-align: left; + padding-right: 0px; + -webkit-border-top-left-radius: 4px; + -webkit-border-bottom-left-radius: 4px; + -moz-border-radius-topleft: 4px; + -moz-border-radius-bottomleft: 4px; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; +} +.toolkit-scale.toolkit-vertical.toolkit-left > .toolkit-label { + left: 6px; + margin-right: 6px; +} +.toolkit-scale.toolkit-vertical.toolkit-left > .toolkit-label.toolkit-base { + +} +.toolkit-scale.toolkit-vertical.toolkit-left > .toolkit-label.toolkit-min { + +} +.toolkit-scale.toolkit-vertical.toolkit-left > .toolkit-label.toolkit-max { + +} +.toolkit-scale.toolkit-vertical.toolkit-left > .toolkit-dot { + left: 0; +} +.toolkit-scale.toolkit-vertical.toolkit-left > .toolkit-dot.toolkit-base { + +} +.toolkit-scale.toolkit-vertical.toolkit-left > .toolkit-dot.toolkit-min { + +} +.toolkit-scale.toolkit-vertical.toolkit-left > .toolkit-dot.toolkit-max { + +} +.toolkit-scale.toolkit-vertical.toolkit-left > .toolkit-dot.toolkit-marker { + +} +/* VERTICAL sticked to RIGHT */ + +.toolkit-scale.toolkit-vertical.toolkit-right { + text-align: right; + padding-left: 0px; + -webkit-border-top-right-radius: 4px; + -webkit-border-bottom-right-radius: 4px; + -moz-border-radius-topright: 4px; + -moz-border-radius-bottomright: 4px; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; +} +.toolkit-scale.toolkit-vertical.toolkit-right > .toolkit-label { + right: 6px; + margin-left: 6px; +} +.toolkit-scale.toolkit-vertical.toolkit-right > .toolkit-label.toolkit-base { + +} +.toolkit-scale.toolkit-vertical.toolkit-right > .toolkit-label.toolkit-min { + +} +.toolkit-scale.toolkit-vertical.toolkit-right > .toolkit-label.toolkit-max { + +} +.toolkit-scale.toolkit-vertical.toolkit-right > .toolkit-dot { + right: 0; +} +.toolkit-scale.toolkit-vertical.toolkit-right > .toolkit-dot.toolkit-base { + +} +.toolkit-scale.toolkit-vertical.toolkit-right > .toolkit-dot.toolkit-min { + +} +.toolkit-scale.toolkit-vertical.toolkit-right > .toolkit-dot.toolkit-max { + +} +.toolkit-scale.toolkit-vertical.toolkit-right > .toolkit-dot.toolkit-marker { + +} +/* HORIZONTAL */ + +.toolkit-scale.toolkit-horizontal { + height: 24px; +} +.toolkit-scale.toolkit-horizontal > .toolkit-label { +} +.toolkit-scale.toolkit-horizontal > .toolkit-label.toolkit-base { + +} +.toolkit-scale.toolkit-horizontal > .toolkit-label.toolkit-min { +} +.toolkit-scale.toolkit-horizontal > .toolkit-label.toolkit-max { +} +.toolkit-scale.toolkit-horizontal > .toolkit-dot { + height: 2px; +} +.toolkit-scale.toolkit-horizontal > .toolkit-dot.toolkit-base { + height: 4px; +} +.toolkit-scale.toolkit-horizontal > .toolkit-dot.toolkit-min { + height: 4px; +} +.toolkit-scale.toolkit-horizontal > .toolkit-dot.toolkit-max { + height: 4px; +} +.toolkit-scale.toolkit-horizontal > .toolkit-dot.toolkit-marker { + height: 4px; +} +/* HORIZONTAL sticked to BOTTOM */ + +.toolkit-scale.toolkit-horizontal.toolkit-bottom { + +} +.toolkit-scale.toolkit-horizontal.toolkit-bottom > .toolkit-label { + margin-top: 8px; + bottom: 6px; +} +.toolkit-scale.toolkit-horizontal.toolkit-bottom > .toolkit-label.toolkit-base { + +} +.toolkit-scale.toolkit-horizontal.toolkit-bottom > .toolkit-label.toolkit-min { + +} +.toolkit-scale.toolkit-horizontal.toolkit-bottom > .toolkit-label.toolkit-max { + +} +.toolkit-scale.toolkit-horizontal.toolkit-bottom > .toolkit-dot { + bottom: 0; +} +.toolkit-scale.toolkit-horizontal.toolkit-bottom > .toolkit-dot.toolkit-base { + +} +.toolkit-scale.toolkit-horizontal.toolkit-bottom > .toolkit-dot.toolkit-min { + +} +.toolkit-scale.toolkit-horizontal.toolkit-bottom > .toolkit-dot.toolkit-max { + +} +.toolkit-scale.toolkit-horizontal.toolkit-bottom > .toolkit-dot.toolkit-marker { + +} +/* HORIZONTAL sticked to TOP */ + +.toolkit-scale.toolkit-horizontal.toolkit-top { + +} +.toolkit-scale.toolkit-horizontal.toolkit-top > .toolkit-label { + margin-bottom: 8px; + top: 6px; +} +.toolkit-scale.toolkit-horizontal.toolkit-top > .toolkit-label.toolkit-base { + +} +.toolkit-scale.toolkit-horizontal.toolkit-top > .toolkit-label.toolkit-min { + +} +.toolkit-scale.toolkit-horizontal.toolkit-top > .toolkit-label.toolkit-max { + +} +.toolkit-scale.toolkit-horizontal.toolkit-top > .toolkit-dot { + top: 0; +} +.toolkit-scale.toolkit-horizontal.toolkit-top > .toolkit-dot.toolkit-base { + +} +.toolkit-scale.toolkit-horizontal.toolkit-top > .toolkit-dot.toolkit-min { + +} +.toolkit-scale.toolkit-horizontal.toolkit-top > .toolkit-dot.toolkit-max { + +} +.toolkit-scale.toolkit-horizontal.toolkit-top > .toolkit-dot.toolkit-marker { + +} + + +/* MARKER */ + +.toolkit-scale.toolkit-left > .toolkit-pointer { + border-right-color: #BA1B25; +} +.toolkit-scale.toolkit-right > .toolkit-pointer { + border-left-color: #BA1B25; +} +.toolkit-scale.toolkit-top > .toolkit-pointer { + border-bottom-color: #BA1B25; +} +.toolkit-scale.toolkit-bottom > .toolkit-pointer { + border-top-color: #BA1B25; +} +.toolkit-scale > .toolkit-bar { + background: #002f42; +} + +/* BAR */ + +.toolkit-scale.toolkit-vertical > .toolkit-bar { + width: 4px; +} +.toolkit-scale.toolkit-horizontal > .toolkit-bar { + height: 4px; +} diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/select.css b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/select.css new file mode 100644 index 0000000000..f56a5847ab --- /dev/null +++ b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/select.css @@ -0,0 +1,63 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +.toolkit-button.toolkit-select { + padding-left: 8px; + padding-right: 8px; +} + +.toolkit-button.toolkit-select > .toolkit-cell > .toolkit-label { + margin-right: 20px; +} +.toolkit-button.toolkit-select > .toolkit-cell > .toolkit-arrow { + background: url(../images/select/arrow_down.png) 50% 50% no-repeat; + width: 16px; + position: absolute; + top: 0; + right: 0; + bottom: 0; +} + +.toolkit-select-list { + background: #efefef; + border: 1px solid white; + margin: 0; + padding: 0; + -webkit-box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.2); + -moz-box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.2); + box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.2); + transition: opacity 200ms; + text-align: left; +} + +.toolkit-select-list > .toolkit-option { + list-style: none; + margin: 0; + padding: 0 5px; + line-height: 2em; + cursor: pointer; + transition: background 0.25s ease-in-out, color 0.25s ease-in-out; +} + +.toolkit-select-list > .toolkit-option:hover { + background: #002f42; + color: white; +} +.toolkit-select-list > .toolkit-option.toolkit-active { + background: #dfdfdf; +} diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/slider.css b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/slider.css new file mode 100644 index 0000000000..e0aee775b5 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/slider.css @@ -0,0 +1,21 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +.toolkit-slider { + +} diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/state.css b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/state.css new file mode 100644 index 0000000000..3e5aa9eac4 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/state.css @@ -0,0 +1,49 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +/* +available overlays: +circle.png +square.png +vertical.png +horizontal.png +*/ + +.toolkit-state { + width: 32px; + height: 10px; + margin: 10px; + background-color: white; + background-image: url(../images/state/circle.png); + background-position: 0 0; + background-size: 100% 100%; + background-clip: padding-box !important; + border-top: 3px solid #ddd; + border-bottom: 3px solid #fff; + border-left: 2px solid #e8e8e8; + border-right: 2px solid #f3f3f3; +} + +.toolkit-state > .toolkit-mask { + background: black; + opacity: 0.8; +} + +.toolkit-state.toolkit-state-on > .toolkit-mask { + opacity: 0; +} diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/toggle.css b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/toggle.css new file mode 100644 index 0000000000..75e7b28bb6 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/toggle.css @@ -0,0 +1,42 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +.toolkit-toggle { + +} +.toolkit-toggle:hover { + +} +.toolkit-toggle:active { + +} +.toolkit-toggle > .toolkit-cell > .toolkit-label { + +} +.toolkit-toggle > .toolkit-cell > .toolkit-icon { + +} +.toolkit-toggle.active { + +} +.toolkit-toggle.active > .toolkit-cell > .toolkit-label { + +} +.toolkit-toggle.active > .toolkit-cell > .toolkit-icon { + +} diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/tooltip.css b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/tooltip.css new file mode 100644 index 0000000000..92936dfb5c --- /dev/null +++ b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/tooltip.css @@ -0,0 +1,47 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +.toolkit-tooltip { + +} +.toolkit-tooltip > .toolkit-table { + +} +.toolkit-tooltip > .toolkit-table > .toolkit-row { + +} +.toolkit-tooltip > .toolkit-table > .toolkit-row > .toolkit-cell { + +} + +.toolkit-tooltip > .toolkit-table > .toolkit-row > .toolkit-cell > .toolkit-entry { + background: #e7e7e7; + color: black; + opacity: 0.9; + font-size: 0.9em; + color: #666; + padding: 2px 4px; + -webkit-box-shadow: 0px 2px 5px rgba(0,0,0,0.3); + -moz-box-shadow: 0px 2px 5px rgba(0,0,0,0.3); + box-shadow: 0px 2px 5px rgba(0,0,0,0.3); + border: 1px solid #f3f3f3; +} + +.toolkit-tt-container { + padding: 24px 0 0 24px; +} diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/value.css b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/value.css new file mode 100644 index 0000000000..a24db0dd44 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/value.css @@ -0,0 +1,47 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +.toolkit-value > .toolkit-input { +/* + border-top: 1px solid #ddd; + border-bottom: 1px solid #fff; + border-left: 1px solid #e8e8e8; + border-right: 1px solid #f3f3f3; +*/ + border: none; + color: #002f42; + background: rgba(0, 0, 0, 0.05); + font-size: 1.2em; + font-family: inherit; + line-height: 1.1em; + padding: 0; + margin: 0; + outline: 0; + text-align: center; + overflow: hidden; + width: 100%; + cursor: pointer; +} + +.toolkit-value.toolkit-active > .toolkit-input { + background: #002a42; + color: white; +/* + border: 1px solid transparent; +*/ +} diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/valuebutton.css b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/valuebutton.css new file mode 100644 index 0000000000..e9dee2769b --- /dev/null +++ b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/valuebutton.css @@ -0,0 +1,88 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +.toolkit-valuebutton { + padding: 0; + + grid-column-gap: 8px; + grid-row-gap: 0px; +} + +.toolkit-valuebutton.toolkit-dragging, +.toolkit-valuebutton.toolkit-scrolling { + border: 1px solid #002f42; +} +.toolkit-valuebutton.toolkit-warn { + border: 1px solid #c00; +} + +.toolkit-valuebutton > .toolkit-icon { + margin-left: 8px; + margin-top: 4px; + margin-bottom: 4px; +} +.toolkit-valuebutton > .toolkit-label { + margin-top: 4px; + margin-bottom: 4px; +} +.toolkit-valuebutton > .toolkit-scale { + margin-left: 8px; + margin-bottom: 4px; + height: 16px; +} +.toolkit-valuebutton > .toolkit-scale::before { + content: ""; + display: block; + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 4px; + background: white; +} + +.toolkit-valuebutton > .toolkit-scale > .toolkit-dot { + height: 4px; +} + +.toolkit-valuebutton > .toolkit-value { + background: rgba(0,0,0,0.033); + border: none; + -webkit-border-top-right-radius: 5px; + -webkit-border-bottom-right-radius: 5px; + -moz-border-radius-topright: 5px; + -moz-border-radius-bottomright: 5px; + border-top-right-radius: 5px; + border-bottom-right-radius: 5px; + + justify-self: stretch; + align-self: stretch; +} +.toolkit-valuebutton > .toolkit-value > .toolkit-input { + width: 100%; + height: 100%; +} +.toolkit-valuebutton.toolkit-dragging > .toolkit-label, +.toolkit-valuebutton.toolkit-scrolling > .toolkit-label { + background-color: #002f42; + color: white; + text-shadow: 0px -1px 0px black; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/valueknob.css b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/valueknob.css new file mode 100644 index 0000000000..c79583a80d --- /dev/null +++ b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/valueknob.css @@ -0,0 +1,22 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +.toolkit-valueknob { + +} diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/window.css b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/window.css new file mode 100644 index 0000000000..343c65a43b --- /dev/null +++ b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/css/window.css @@ -0,0 +1,117 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +.toolkit-window { + border: 1px solid #fff; + background: #eee url(../images/window/background.png) no-repeat; + -webkit-box-shadow: 0px 4px 12px 0px rgba(0, 0, 0, 0.33); + -moz-box-shadow: 0px 4px 12px 0px rgba(0, 0, 0, 0.33); + box-shadow: 0px 4px 12px 0px rgba(0, 0, 0, 0.33); + opacity: 1; + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; +} +.toolkit-window.toolkit-inactive { + opacity: 0.5; + -webkit-box-shadow: 0px 3px 10px 0px rgba(0, 0, 0, 0.2); + -moz-box-shadow: 0px 3px 10px 0px rgba(0, 0, 0, 0.2); + box-shadow: 0px 3px 10px 0px rgba(0, 0, 0, 0.2); +} + +.toolkit-window.toolkit-resizing, +.toolkit-window.toolkit-dragging { + opacity: 0.7; +} + +.toolkit-window > .toolkit-content { + padding: 4px; +} + +.toolkit-window > .toolkit-header { + height: 32px; + line-height: 30px; + border-bottom: 1px solid rgba(0,0,0,0.033); + background: rgba(255,255,255,0.33); +} +.toolkit-window > .toolkit-footer { + height: 28px; + line-height: 28px; + border-top: 1px solid rgba(255,255,255,0.6); + background: rgba(255,255,255,0.33); +} + +.toolkit-window > .toolkit-header > .toolkit-title, +.toolkit-window > .toolkit-footer > .toolkit-title { + font-weight: 700; + text-align: left; + font-size: 14px; + color: #999; + text-shadow: 0px 1px 0px white; + margin: 0 5px; +} +.toolkit-window.toolkit-active > .toolkit-header > .toolkit-title, +.toolkit-window.toolkit-active > .toolkit-footer > .toolkit-title { + color: #002f42; +} +.toolkit-window > .toolkit-header .toolkit-status, +.toolkit-window > .toolkit-footer .toolkit-status { + color: #999; + margin: 0 5px; + text-align: left; +} +.toolkit-window > .toolkit-header > .toolkit-icon, +.toolkit-window > .toolkit-footer > .toolkit-icon { + width: 24px; + height: 24px; + margin: 2px; +} +.toolkit-window > .toolkit-header > .toolkit-button, +.toolkit-window > .toolkit-footer > .toolkit-button { + margin: 0 2px; + padding: 0px; + border: 0px solid transparent; + background: transparent; + transition: border-color 0.33s; + border-radius: 0; + box-shadow: none; +} +.toolkit-window > .toolkit-header > .toolkit-button:hover, +.toolkit-window > .toolkit-footer > .toolkit-button:hover { + background: white; +} +.toolkit-window > .toolkit-header > .toolkit-button .toolkit-icon, +.toolkit-window > .toolkit-footer > .toolkit-button .toolkit-icon { + height: 16px; + margin: 0; + font-size: 16px; + line-height: 16px; + text-shadow: none !important; +} +.toolkit-window > .toolkit-header > .toolkit-button.toolkit-close .toolkit-icon, +.toolkit-window > .toolkit-footer > .toolkit-button.toolkit-close .toolkit-icon { + color: #BA1B25 !important; +} + +.toolkit-window.toolkit-shrinked { + height: 32px !important; +} +.toolkit-window > .toolkit-header > .toolkit-size, +.toolkit-window > .toolkit-footer > .toolkit-size { + color: #ccc; +} diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/background.png b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/background.png new file mode 100644 index 0000000000..43d7a2c8cc Binary files /dev/null and b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/background.png differ diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/chart/background.png b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/chart/background.png new file mode 100644 index 0000000000..692047f87e Binary files /dev/null and b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/chart/background.png differ diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/chart/background.xcf b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/chart/background.xcf new file mode 100644 index 0000000000..41cae6dbb6 Binary files /dev/null and b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/chart/background.xcf differ diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/fader/background_horizontal_center.png b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/fader/background_horizontal_center.png new file mode 100644 index 0000000000..4fae7c545c Binary files /dev/null and b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/fader/background_horizontal_center.png differ diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/fader/background_horizontal_left.png b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/fader/background_horizontal_left.png new file mode 100644 index 0000000000..dabb85a283 Binary files /dev/null and b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/fader/background_horizontal_left.png differ diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/fader/background_horizontal_right.png b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/fader/background_horizontal_right.png new file mode 100644 index 0000000000..03f6a8770d Binary files /dev/null and b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/fader/background_horizontal_right.png differ diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/fader/background_vertical_bottom.png b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/fader/background_vertical_bottom.png new file mode 100644 index 0000000000..7b7809f2ca Binary files /dev/null and b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/fader/background_vertical_bottom.png differ diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/fader/background_vertical_center.png b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/fader/background_vertical_center.png new file mode 100644 index 0000000000..3e4ef97eff Binary files /dev/null and b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/fader/background_vertical_center.png differ diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/fader/background_vertical_top.png b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/fader/background_vertical_top.png new file mode 100644 index 0000000000..bb954469e0 Binary files /dev/null and b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/fader/background_vertical_top.png differ diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/fader/handle_horizontal.png b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/fader/handle_horizontal.png new file mode 100644 index 0000000000..01af977a9a Binary files /dev/null and b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/fader/handle_horizontal.png differ diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/fader/handle_horizontal_warn.png b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/fader/handle_horizontal_warn.png new file mode 100644 index 0000000000..b690555457 Binary files /dev/null and b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/fader/handle_horizontal_warn.png differ diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/fader/handle_vertical.png b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/fader/handle_vertical.png new file mode 100644 index 0000000000..6c58d4d942 Binary files /dev/null and b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/fader/handle_vertical.png differ diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/fader/handle_vertical_warn.png b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/fader/handle_vertical_warn.png new file mode 100644 index 0000000000..2fb40e9c17 Binary files /dev/null and b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/fader/handle_vertical_warn.png differ diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/gradients/blue_in.jpg b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/gradients/blue_in.jpg new file mode 100644 index 0000000000..c159c3e468 Binary files /dev/null and b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/gradients/blue_in.jpg differ diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/gradients/blue_in.png b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/gradients/blue_in.png new file mode 100644 index 0000000000..34c7810911 Binary files /dev/null and b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/gradients/blue_in.png differ diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/gradients/blue_out.jpg b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/gradients/blue_out.jpg new file mode 100644 index 0000000000..616fbe10c5 Binary files /dev/null and b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/gradients/blue_out.jpg differ diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/gradients/blue_out.png b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/gradients/blue_out.png new file mode 100644 index 0000000000..7fd8e09b64 Binary files /dev/null and b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/gradients/blue_out.png differ diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/gradients/grey_in.jpg b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/gradients/grey_in.jpg new file mode 100644 index 0000000000..3479851f20 Binary files /dev/null and b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/gradients/grey_in.jpg differ diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/gradients/grey_in.png b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/gradients/grey_in.png new file mode 100644 index 0000000000..f37cdc6832 Binary files /dev/null and b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/gradients/grey_in.png differ diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/gradients/grey_out.jpg b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/gradients/grey_out.jpg new file mode 100644 index 0000000000..42783a5854 Binary files /dev/null and b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/gradients/grey_out.jpg differ diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/gradients/grey_out.png b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/gradients/grey_out.png new file mode 100644 index 0000000000..97b8c046b6 Binary files /dev/null and b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/gradients/grey_out.png differ diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/knob/background.svg b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/knob/background.svg new file mode 100644 index 0000000000..a6317c45f8 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/knob/background.svg @@ -0,0 +1,313 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/meterbase/bevel_soft_horizontal.png b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/meterbase/bevel_soft_horizontal.png new file mode 100644 index 0000000000..dfb5c95ba7 Binary files /dev/null and b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/meterbase/bevel_soft_horizontal.png differ diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/meterbase/bevel_soft_vertical.png b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/meterbase/bevel_soft_vertical.png new file mode 100644 index 0000000000..a370a6292a Binary files /dev/null and b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/meterbase/bevel_soft_vertical.png differ diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/meterbase/bevel_strong_horizontal.png b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/meterbase/bevel_strong_horizontal.png new file mode 100644 index 0000000000..9c0ba252ae Binary files /dev/null and b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/meterbase/bevel_strong_horizontal.png differ diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/meterbase/bevel_strong_vertical.png b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/meterbase/bevel_strong_vertical.png new file mode 100644 index 0000000000..ad6eabfd84 Binary files /dev/null and b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/meterbase/bevel_strong_vertical.png differ diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/meterbase/grid_soft.png b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/meterbase/grid_soft.png new file mode 100644 index 0000000000..9584a7c64f Binary files /dev/null and b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/meterbase/grid_soft.png differ diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/meterbase/grid_strong.png b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/meterbase/grid_strong.png new file mode 100644 index 0000000000..af3c7290fa Binary files /dev/null and b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/meterbase/grid_strong.png differ diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/meterbase/led_horizontal.png b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/meterbase/led_horizontal.png new file mode 100644 index 0000000000..76d1fe0c0c Binary files /dev/null and b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/meterbase/led_horizontal.png differ diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/meterbase/led_vertical.png b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/meterbase/led_vertical.png new file mode 100644 index 0000000000..dad90cc69d Binary files /dev/null and b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/meterbase/led_vertical.png differ diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/meterbase/slim_soft_horizontal.png b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/meterbase/slim_soft_horizontal.png new file mode 100644 index 0000000000..b8d3d0bd80 Binary files /dev/null and b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/meterbase/slim_soft_horizontal.png differ diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/meterbase/slim_soft_vertical.png b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/meterbase/slim_soft_vertical.png new file mode 100644 index 0000000000..f3780b707d Binary files /dev/null and b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/meterbase/slim_soft_vertical.png differ diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/meterbase/slim_strong_horizontal.png b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/meterbase/slim_strong_horizontal.png new file mode 100644 index 0000000000..bd23f7ebaf Binary files /dev/null and b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/meterbase/slim_strong_horizontal.png differ diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/meterbase/slim_strong_vertical.png b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/meterbase/slim_strong_vertical.png new file mode 100644 index 0000000000..691b9a0e71 Binary files /dev/null and b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/meterbase/slim_strong_vertical.png differ diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/select/arrow_down.png b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/select/arrow_down.png new file mode 100644 index 0000000000..674dee8b1a Binary files /dev/null and b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/select/arrow_down.png differ diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/state/circle.png b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/state/circle.png new file mode 100644 index 0000000000..c2c7f31f0c Binary files /dev/null and b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/state/circle.png differ diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/state/horizontal.png b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/state/horizontal.png new file mode 100644 index 0000000000..e4568beaab Binary files /dev/null and b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/state/horizontal.png differ diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/state/over.xcf b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/state/over.xcf new file mode 100644 index 0000000000..dba97587c7 Binary files /dev/null and b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/state/over.xcf differ diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/state/square.png b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/state/square.png new file mode 100644 index 0000000000..9011a78ab4 Binary files /dev/null and b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/state/square.png differ diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/state/vertical.png b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/state/vertical.png new file mode 100644 index 0000000000..dc2f7c29ab Binary files /dev/null and b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/state/vertical.png differ diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/window/background.png b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/window/background.png new file mode 100644 index 0000000000..94c03e9f69 Binary files /dev/null and b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/window/background.png differ diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/window/resize.png b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/window/resize.png new file mode 100644 index 0000000000..02cba4790e Binary files /dev/null and b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/images/window/resize.png differ diff --git a/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/theme.css b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/theme.css new file mode 100644 index 0000000000..0eb7878994 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/ardour-toolkit-theme/theme.css @@ -0,0 +1,41 @@ +@import "../toolkit/styles/toolkit.css"; + +@import "css/root.css"; +@import "css/globalcursor.css"; +@import "css/tooltip.css"; +@import "css/scale.css"; +@import "css/graph.css"; +@import "css/grid.css"; +@import "css/responsehandle.css"; +@import "css/circular.css"; +@import "css/buttonarray.css"; +@import "css/meterbase.css"; +@import "css/chart.css"; +@import "css/gauge.css"; +@import "css/button.css"; +@import "css/valuebutton.css"; +@import "css/toggle.css"; +@import "css/state.css"; +@import "css/levelmeter.css"; +@import "css/frequencyresponse.css"; +@import "css/dynamics.css"; +@import "css/responsehandler.css"; +@import "css/clock.css"; +@import "css/window.css"; +@import "css/knob.css"; +@import "css/value.css"; +@import "css/fader.css"; +@import "css/select.css"; +@import "css/label.css"; +@import "css/container.css"; +@import "css/pager.css"; +@import "css/expander.css"; +@import "css/valueknob.css"; +@import "css/multimeter.css"; +@import "css/notification.css"; +@import "css/notifications.css"; +@import "css/crossover.css"; +@import "css/frame.css"; +@import "css/dialog.css"; +@import "css/colorpicker.css"; +@import "css/colorpickerdialog.css"; diff --git a/share/web_surfaces/builtin/mixer/index.html b/share/web_surfaces/builtin/mixer/index.html new file mode 100644 index 0000000000..4b725d6ae0 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/index.html @@ -0,0 +1,12 @@ + + + + + Ardour Mixer + + + + + + + diff --git a/share/web_surfaces/builtin/mixer/js/main.js b/share/web_surfaces/builtin/mixer/js/main.js new file mode 100644 index 0000000000..d0d8bb6918 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/js/main.js @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2020 Luciano Iam + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +import ArdourClient from '/shared/ardour.js'; +import { createRootContainer, Container, DiscreteKnob, LinearKnob, PanKnob, + StripGainFader, StripMeter, Toggle } from './tkwidget.js'; + +(() => { + + const ardour = new ArdourClient(); + + async function main () { + const root = await createRootContainer(); + + ardour.mixer.on('ready', () => { + if (root.children.length > 0) { + root.removeChild(root.children[0]); + } + + const mixer = new Container(); + mixer.id = 'mixer'; + mixer.appendTo(root); + + mixer.appendChild(new Container()); + + for (const strip of ardour.mixer.strips) { + const container = new Container(); + container.classList = 'strip'; + container.appendTo(mixer); + createStrip(strip, container); + } + + mixer.appendChild(new Container()); + }); + + ardour.connect(); + } + + function createStrip (strip, container) { + const pan = new PanKnob(); + pan.classList += 'pan'; + pan.appendTo(container); + if (strip.isVca) { + // hide pan, keeping layout + pan.element.style.visibility = 'hidden'; + } else { + pan.bindTo(strip, 'pan'); + } + + const meterFader = new Container(); + meterFader.appendTo(container); + meterFader.classList = 'strip-meter-fader'; + + const gain = new StripGainFader(); + gain.appendTo(meterFader); + gain.bindTo(strip, 'gain'); + + const meter = new StripMeter(); + meter.appendTo(meterFader); + meter.bindTo(strip, 'meter'); + + // TO DO + /*for (const plugin of strip.plugins) { + createStripPlugin(plugin, container); + }*/ + } + + function createStripPlugin (plugin, container) { + const enable = new Toggle(); + enable.appendTo(container); + enable.bindTo(plugin, 'enable'); + + for (const param of plugin.parameters) { + createStripPluginParam(param, container); + } + } + + function createStripPluginParam (param, container) { + let widget; + + if (param.valueType.isBoolean) { + widget = new Toggle(); + } else if (param.valueType.isInteger) { + widget = new DiscreteKnob(param.min, param.max); + } else if (param.valueType.isDouble) { + if (param.isLog) { + widget = new LogKnob(param.min, param.max); + } else { + widget = new LinearKnob(param.min, param.max); + } + } + + widget.appendTo(container); + widget.bindTo(param, 'value'); + } + + main(); + +})(); diff --git a/share/web_surfaces/builtin/mixer/js/scale.js b/share/web_surfaces/builtin/mixer/js/scale.js new file mode 100644 index 0000000000..4682df1d9a --- /dev/null +++ b/share/web_surfaces/builtin/mixer/js/scale.js @@ -0,0 +1,82 @@ +/* + * Copyright © 2020 Luciano Iam + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +// This module is currently unused as 'toolkit' provides its own transformations +// it could be still useful for developing custom widgets + +export class Scale { + + constructor (min, max) { + this.min = min; + this.max = max; + this.scale = this.max - this.min; + } + + fromWidget (val) { + return val; + } + + toWidget (val) { + return val; + } + +} + +export class DbScale extends Scale { + + constructor () { + super(-58.0, 6.0); + } + + fromWidget (val) { + return this.max + this.scale * Math.log10(val); + } + + toWidget (val) { + return Math.pow(10.0, (val - this.max) / this.scale); + } + +} + +export class LinearScale extends Scale { + + fromWidget (val) { + return this.min + this.scale * val; + } + + toWidget (val) { + return (val - this.min) / this.scale; + } + +} + +export class LogScale extends Scale { + + constructor (min, max) { + super(Math.log(min), Math.log(max)); + } + + fromWidget (val) { + return Math.exp(this.min + this.scale * val); + } + + toWidget (val) { + return (Math.log(val) - this.min) / this.scale; + } + +} diff --git a/share/web_surfaces/builtin/mixer/js/tkloader.js b/share/web_surfaces/builtin/mixer/js/tkloader.js new file mode 100644 index 0000000000..f4b29e4c98 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/js/tkloader.js @@ -0,0 +1,90 @@ +/* + * Copyright © 2020 Luciano Iam + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +// This is a convenience module for loading the 'toolkit' audio widget library +// https://github.com/DeutscheSoft/toolkit +// GPLv3 license, docs at http://docs.deuso.de/Toolkit/ + +const STYLES = []; + +const SCRIPTS = [ + 'G', + 'toolkit', + 'implements/audiomath', + 'implements/base', + 'implements/globalcursor', + 'implements/ranged', + 'implements/gradient', + 'implements/warning', + 'widgets/widget', + 'modules/circular', + 'modules/dragcapture', + 'modules/dragvalue', + 'modules/range', + 'modules/scale', + 'modules/scrollvalue', + 'widgets/icon', + 'widgets/label', + 'widgets/button', + 'widgets/container', + 'widgets/meterbase', + 'widgets/state', + 'widgets/toggle', + 'widgets/levelmeter', + 'widgets/value', + 'widgets/fader', + 'widgets/knob', + 'widgets/root' +]; + +export default async function loadToolkit() { + const tkPath = '../toolkit'; + // stylesheets can be loaded in parallel + const p = STYLES.map((style) => loadStylesheet(import.meta, `${tkPath}/styles/${style}.css`)); + await Promise.all(p); + // scripts need to be loaded sequentially + for (const script of SCRIPTS) { + await loadScript(import.meta, `${tkPath}/${script}.js`); + } +} + +async function loadStylesheet(importMeta, path) { + return new Promise((resolve, reject) => { + const link = document.createElement('link'); + link.rel = 'stylesheet'; + link.type = 'text/css'; + link.href = `${getModuleBasename(importMeta)}/${path}`; + link.addEventListener('error', (ev) => reject(Error('Stylesheet failed to load'))); + link.addEventListener('load', resolve); + document.head.appendChild(link); + }); +} + +async function loadScript(importMeta, path) { + return new Promise((resolve, reject) => { + const script = document.createElement('script'); + script.src = `${getModuleBasename(importMeta)}/${path}`; + script.addEventListener('error', (ev) => reject(Error('Script failed to load'))); + script.addEventListener('load', resolve); + document.body.appendChild(script); + }); +} + +function getModuleBasename(importMeta) { + return importMeta.url.substring(0, importMeta.url.lastIndexOf('/')); +} diff --git a/share/web_surfaces/builtin/mixer/js/tkwidget.js b/share/web_surfaces/builtin/mixer/js/tkwidget.js new file mode 100644 index 0000000000..98365ab482 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/js/tkwidget.js @@ -0,0 +1,185 @@ +/* + * Copyright © 2020 Luciano Iam + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +import loadToolkit from './tkloader.js'; +import { BaseContainer, BaseControl } from './widget.js'; + +class Control extends BaseControl { + + get element () { + return this.tk.element; + } + +} + +class RangeControl extends Control { + + constructor (tk) { + super(); + + this.tk = tk; + this.lastValue = NaN; + + this.tk.add_event('useraction', (name, value) => { + if ((name == 'value') && (this.lastValue != value)) { + this.lastValue = value; + this.callback(value); + } + }); + } + + get value () { + return this.tk.get('value'); + } + + set value (val) { + this.tk.set('value', val); + } + +} + +class Knob extends RangeControl { + + constructor (options) { + super(new TK.Knob(options)); + } + +} + +export async function createRootContainer () { + await loadToolkit(); + const root = new Container(); + root.tk = new TK.Root({id: 'root'}); + document.body.appendChild(root.element); + return root; +} + +export class Container extends BaseContainer { + + constructor () { + super(); + this.tk = new TK.Container(); + } + + get element () { + return this.tk.element; + } + + appendChild (child) { + super.appendChild(child); + this.tk.append_child(child.tk); + } + +} + +export class Toggle extends Control { + + constructor () { + super(new TK.Toggle()); + this.tk.add_event('toggled', (state) => this.callback(state)); + } + + get value () { + return this.tk.get('state'); + } + + set value (val) { + this.tk.set('state', val); + } + +} + +export class StripGainFader extends RangeControl { + + constructor () { + super(new TK.Fader({ + scale: 'decibel', + min: -58.0, + max: 6.0 + })); + } + +} + +export class StripMeter extends RangeControl { + + constructor () { + super(new TK.LevelMeter({ + show_scale: false, + scale: 'decibel', + min: -58.0, + max: 6.0 + })); + } + + set value (val) { + this.tk.set('value', val); + } + +} + +export class LinearKnob extends Knob { + + constructor (min, max) { + super({ + scale: 'linear', + min: min, + max: max + }); + } + +} + +export class LogKnob extends Knob { + + constructor (min, max) { + super({ + scale: 'log2', + min: min, + max: max + }); + } + +} + +export class DiscreteKnob extends Knob { + + constructor (min, max, step) { + super({ + scale: 'linear', + min: min, + max: max, + snap: step || 1 + }); + } + +} + +// TODO: consider switching to [0-1.0] pan scale in Ardour surface code + +export class PanKnob extends Knob { + + constructor () { + super({ + scale: 'linear', + min: -1.0, + max: 1.0 + }); + } + +} diff --git a/share/web_surfaces/builtin/mixer/js/widget.js b/share/web_surfaces/builtin/mixer/js/widget.js new file mode 100644 index 0000000000..a4541d8677 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/js/widget.js @@ -0,0 +1,91 @@ +/* + * Copyright © 2020 Luciano Iam + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +export class BaseWidget { + + get element () { + // empty + } + + appendTo (container) { + container.appendChild(this); + } + + get id () { + return this.element.id; + } + + set id (id) { + this.element.id = id; + } + + get classList () { + return this.element.classList; + } + + set classList (classList) { + this.element.classList = classList; + } + +} + +export class BaseContainer extends BaseWidget { + + constructor (context) { + super(context); + this.children = []; + } + + appendChild (child) { + this.children.push(child); + } + +} + +export class BaseControl extends BaseWidget { + + get value () { + // empty + } + + set value (val) { + // empty + } + + callback (val) { + // empty + } + + bindTo (component, property) { + // ardour → ui + this.value = component[property]; + component.on(property, (value) => this.value = value); + // ui → ardour + this.callback = (value) => component[property] = value; + } + +} + +// Currently unused + +export function createElement (html) { + const t = document.createElement('template'); + t.innerHTML = html; + const elem = t.content.firstChild; + return elem; +} diff --git a/share/web_surfaces/builtin/mixer/main.css b/share/web_surfaces/builtin/mixer/main.css new file mode 100644 index 0000000000..d16fb48e12 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/main.css @@ -0,0 +1,61 @@ +@import "ardour-toolkit-theme/theme.css"; + +html { + height: 100%; +} + +body { + background: rgb(62,61,61); + color: rgb(248,248,242); + font-family: Helvetica, Arial, sans-serif !important; + height: 100%; + width: 100%; + position: fixed; + margin: 0; +} + +div { + box-sizing: border-box; +} + +#root { + display: flex; + width: 100%; + height: 100%; + align-items: center; + justify-content: center; +} + +#mixer { + display: flex; + flex-direction: row; + height: 100%; + max-height: 480px; + width: 100%; + overflow-x: auto; + justify-content: space-between; +} + +.strip { + display: flex; + flex-direction: column; + align-items: center; + height: 100%; + min-width: 120px; +} + +.strip-meter-fader { + height: 100%; +} + +.toolkit-fader { + height: 100%; +} + +.toolkit-level-meter { + height: 100%; +} + +.toolkit-knob { + /* empty */ +} diff --git a/share/web_surfaces/builtin/mixer/manifest.xml b/share/web_surfaces/builtin/mixer/manifest.xml new file mode 100644 index 0000000000..00ce953026 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/manifest.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/share/web_surfaces/builtin/mixer/toolkit/.gitignore b/share/web_surfaces/builtin/mixer/toolkit/.gitignore new file mode 100644 index 0000000000..265908d19c --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/.gitignore @@ -0,0 +1 @@ +styles/css/toolkit.min.css diff --git a/share/web_surfaces/builtin/mixer/toolkit/COPYING b/share/web_surfaces/builtin/mixer/toolkit/COPYING new file mode 100644 index 0000000000..94a9ed024d --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/share/web_surfaces/builtin/mixer/toolkit/G.js b/share/web_surfaces/builtin/mixer/toolkit/G.js new file mode 100644 index 0000000000..bdbf1b0747 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/G.js @@ -0,0 +1,218 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function(w) { +function Scheduler() { + this.Q_next = this.Q = []; + this.Q_tmp = []; + this.tmp = []; + this.debug = 0; + this.after_frame_cbs = []; + this.frame_count = 0; + this.current_priority = -1; + this.current_cycle = 0; +}; +function low_add(Q, o, prio) { + prio = prio << 1; + var q = Q[prio]; + var len = Q[prio+1]; + if (!q) { + Q[prio] = q = []; + len = 0; + } + q[len++] = o; + Q[prio+1] = len; +} +function low_remove(Q, o, prio) { + prio = prio << 1; + var q = Q[prio]; + var len = Q[prio+1] + if (!q) return; + for (var i = 0; i < len; i++) { + if (o !== q[i]) continue; + /* NOTE: this looks odd, but it's correct */ + q[i] = null; + q[i] = q[--len]; + } + Q[prio+1] = len; +} +function low_has(Q, o, prio) { + prio = prio << 1; + var q = Q[prio]; + var len = Q[prio+1]; + if (!q) return false; + for (var i = 0; i < len; i++) { + if (o === q[i]) return true; + } + return false; +} +Scheduler.prototype = { + run : function() { + var Q = this.Q; + this.Q_next = this.Q_tmp; + var empty; + var runs = 0; + var debug = this.debug; + var calls = 0; + var t; + var i; + + if (debug) t = performance.now(); + + while (!empty) { + runs++; + + this.current_cycle = runs; + + if (runs > 20) throw("something is not right"); + + empty = true; + + for (i = 0; i < Q.length; i+=2) { + var q = Q[i]; + var len = Q[i+1]; + + if (!q || !len) continue; + + empty = false; + this.current_priority = i>>1; + + Q[i] = this.tmp; + Q[i+1] = 0; + this.tmp = q; + + for (var j = 0; j < len; j++) { + try { q[j](); } catch (e) { + TK.warn(q[j], "threw an error", e); + } + q[j] = null; + calls++; + } + } + var after_frame_cbs = this.after_frame_cbs; + + if (after_frame_cbs.length) { + this.after_frame_cbs = []; + empty = false; + + for (i = 0; i < after_frame_cbs.length; i++) + after_frame_cbs[i](); + } + } + + this.Q = this.Q_next; + this.Q_tmp = Q; + + if (debug) { + t = performance.now() - t; + if (t > debug) + TK.log("DOMScheduler did %d runs and %d calls: %f ms", runs, calls, t); + } + + this.running = false; + this.current_priority = -1; + + this.frame_count++; + + return runs; + }, + add : function(o, prio) { + low_add(this.Q, o, prio); + }, + add_next : function(o, prio) { + low_add(this.Q_next, o, prio); + }, + remove : function(o, prio) { + low_remove(this.Q, o, prio); + }, + remove_next : function(o, prio) { + low_remove(this.Q_next, o, prio); + }, + has: function(o, prio) { + return low_has(this.Q, o, prio); + }, + has_next: function(o, prio) { + return low_has(this.Q_next, o, prio); + }, + after_frame: function(fun) { + this.after_frame_cbs.push(fun); + }, + get_frame_count: function() { + return this.frame_count; + }, + get_current_priority: function() { + return this.current_priority; + }, + get_current_cycle: function() { + return this.current_cycle; + }, + describe_now: function() { + if (this.running) { + return TK.sprintf("", + this.get_frame_count(), + this.get_current_priority(), + this.get_current_cycle()); + } else { + return ""; + } + }, +}; +function DOMScheduler() { + Scheduler.call(this); + this.will_render = false; + this.running = false; + this.bound_run = this.run.bind(this); +}; +DOMScheduler.prototype = Object.create(Scheduler.prototype); +DOMScheduler.prototype.add_next = function(o, prio) { + Scheduler.prototype.add_next.call(this, o, prio); + + if (this.will_render) return; + this.will_render = true; + if (this.running) return; + + window.requestAnimationFrame(this.bound_run); +}; +DOMScheduler.prototype.add = function(o, prio) { + Scheduler.prototype.add.call(this, o, prio); + + if (this.will_render) return; + if (this.running) return; + this.will_render = true; + + window.requestAnimationFrame(this.bound_run); +}; +DOMScheduler.prototype.run = function() { + this.will_render = false; + this.running = true; + Scheduler.prototype.run.call(this); + this.running = false; + if (this.will_render) + window.requestAnimationFrame(this.bound_run); +}; +DOMScheduler.prototype.after_frame = function(fun) { + Scheduler.prototype.after_frame.call(this, fun); + if (this.will_render) return; + if (this.running) return; + this.will_render = true; + window.requestAnimationFrame(this.bound_run); +}; +w.Scheduler = Scheduler; +w.DOMScheduler = DOMScheduler; +})(this); diff --git a/share/web_surfaces/builtin/mixer/toolkit/implements/README b/share/web_surfaces/builtin/mixer/toolkit/implements/README new file mode 100644 index 0000000000..f6437538bf --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/implements/README @@ -0,0 +1 @@ +@category: Implements diff --git a/share/web_surfaces/builtin/mixer/toolkit/implements/anchor.js b/share/web_surfaces/builtin/mixer/toolkit/implements/anchor.js new file mode 100644 index 0000000000..3bfbca6dd2 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/implements/anchor.js @@ -0,0 +1,82 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function(w, TK){ +/** + * TK.Anchor provides a single function translate_anchor + * which returns real x and y values from a relative positioning. + * For example positioning a {@link TK.Window} with anchor center + * needs to subtract half of its width from y and half of its height + * from x to appear at the correct position. + * + * @mixin TK.Anchor + */ +TK.Anchor = TK.class({ + /** + * Returns real x and y values from a relative positioning. + * + * @method TK.Anchor#translate_anchor + * + * @param {string} [anchor="top-left"] - Position of the anchor. + * @param {number} x - X position to translate. + * @param {number} y - Y position to translate. + * @param {number} width - Width of the element. + * @param {number} height - Height of the element. + * + * @returns {object} Object with members x and y as numbers + */ + translate_anchor: function (anchor, x, y, width, height) { + switch (anchor) { + case "top-left": + break; + case "top": + x += width / 2; + break; + case "top-right": + x += width; + break; + case "left": + y += height / 2; + break; + case "center": + x += width / 2; + y += height / 2; + break; + case "right": + x += width; + y += height / 2; + break; + case "bottom-left": + y += height; + break; + case "bottom": + x += width / 2; + y += height; + break; + case "bottom-right": + x += width; + y += height; + break; + default: + throw new Error("Unsupported anchor position"); + } + return {x: Math.round(x), y: Math.round(y)}; + } +}); +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/implements/audiomath.js b/share/web_surfaces/builtin/mixer/toolkit/implements/audiomath.js new file mode 100644 index 0000000000..a1a153c297 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/implements/audiomath.js @@ -0,0 +1,324 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +/** + * TK.AudioMath provides a couple of functions for turning + * linear values into logarithmic ones and vice versa. If you need + * an easy convertion between dB or Hz and a linear scale mixin + * this class. + * + * @mixin TK.AudioMath + */ +TK.AudioMath = (function() { + var exp = Math.exp; + var log = Math.log; + var pow = Math.pow; + var MAX = Math.max; + var LN2 = Math.LN2; + var LN10 = Math.LN10; + + function log2(value) { + value = +value; + return +log(value) / LN2; + } + + function log10(value) { + value = +value; + return +log(value) / LN10; + } + + function db2gain(value, factor) { + /** + * Calculates 10^(value / factor). + * Transforms a dBFS value to the corresponding gain. + * + * @function TK.AudioMath#db2gain + * + * @param {number} value - A decibel value in dBFS. + * @param {number} [factor=20] - The factor. + */ + value = +value; + factor = +factor; + + if (!(factor >= 0.0)) factor = 20.0; + + value = +(value / factor); + value = +pow(10.0, value); + + return value; + } + + function gain2db(value, factor) { + /** + * Calculates factor * log10(value). + * Transforms a gain value to the corresponding dBFS value. + * + * @function TK.AudioMath#gain2db + * + * @param {number} value - A gain factor. + * @param {number} [factor=20] - The factor. + */ + value = +value; + factor = +factor; + + if (!(factor >= 0.0)) factor = 20.0; + + value = factor * +log10(value); + + return value; + } + + function db2coef(value, min, max, reverse, factor) { + /** + * Calculates a linear value between 0.0 and 1.0 + * from a value and its lower and upper boundaries in decibels. + * + * @function TK.AudioMath#db2coef + * + * @param {number} value - The value in decibels. + * @param {number} min - The minimum value in decibels. + * @param {number} max - The maximum value in decibels. + * @param {boolean} reverse - If the scale is reversed. + * @param {number} factor - Changes the deflection of the logarithm if other than 1.0. + * + * @returns {number} A value between 0.0 (min) and 1.0 (max). + */ + value = +value; + min = +min; + max = +max; + reverse = reverse|0; + factor = +factor; + var logfac = 1.0; + if (factor == 0.0) factor = 1.0; + else logfac = +MAX(1.0, +pow(2.0, factor) - 1.0); + if (reverse) value = max - (value - min); + value = +log2(1.0 + (value - min) / (max - min) * logfac) / factor; + if (reverse) value = -value + 1.0; + return value; + } + + function coef2db(coef, min, max, reverse, factor) { + /** + * Calculates a value in decibels from a value + * between 0.0 and 1.0 and some lower and upper boundaries in decibels. + * + * @function TK.AudioMath#coef2db + * + * @param {number} coef - A value between 0.0 and 1.0. + * @param {number} min - The minimum value in decibels. + * @param {number} max - The maximum value in decibels. + * @param {boolean} reverse - If the scale is reversed. + * @param {number} factor - Changes the deflection of the logarithm if other than 1.0. + * + * @returns {number} The result in decibels. + */ + coef = +coef; + min = +min; + max = +max; + reverse = reverse|0; + factor = +factor; + var logfac = 1.0; + if (factor == 0.0) factor = 1.0; + else logfac = +MAX(1.0, +pow(2.0, factor) - 1.0); + if (reverse) coef = -coef + 1.0; + coef = (+pow(2.0, coef * factor) - 1.0) / logfac * (max - min) + min; + if (reverse) coef = max - coef + min; + return coef; + } + function db2scale(value, min, max, scale, reverse, factor) { + /** + * Calculates a linear value between 0.0 and scale. + * from a value and its lower and upper boundaries in decibels. + * + * @function TK.AudioMath#db2scale + * + * @param {number} value - The value in decibels. + * @param {number} min - The minimum value in decibels. + * @param {number} max - The maximum value in decibels. + * @param {boolean} reverse - If the scale is reversed. + * @param {number} factor - Changes the deflection of the logarithm if other than 1.0. + * + * @returns {number} A value between 0.0 and scale. + */ + value = +value; + min = +min; + max = +max; + scale = +scale; + reverse = reverse|0; + factor = +factor; + var logfac = 1.0; + if (factor == 0.0) factor = 1.0; + else logfac = +MAX(1.0, +pow(2.0, factor) - 1.0); + if (reverse) value = max - (value - min); + value = +log2(1.0 + (value - min) / (max - min) * logfac) / factor; + if (reverse) value = -value + 1.0; + return value * scale; + } + function scale2db(value, min, max, scale, reverse, factor) { + /** + * Calculates a value in decibels from a value + * between 0.0 and scale and some lower and upper boundaries in decibels. + * + * @function TK.AudioMath#scale2db + * + * @param {number} value - A value between 0.0 and scale. + * @param {number} min - The minimum value in decibels. + * @param {number} max - The maximum value in decibels. + * @param {boolean} reverse - If the scale is reversed. + * @param {number} factor - Changes the deflection of the logarithm if other than 1.0. + * + * @returns {number} The result in decibels. + */ + value = +value; + min = +min; + max = +max; + scale = +scale; + reverse = reverse|0; + factor = +factor; + var logfac = 1.0; + if (factor == 0.0) factor = 1.0; + else logfac = +MAX(1.0, +pow(2.0, factor) - 1.0); + value = value / scale; + if (reverse) value = -value + 1.0; + value = (+pow(2.0, value * factor) - 1.0) / logfac * (max - min) + min; + if (reverse) value = max - value + min; + return value; + } + function freq2coef(value, min, max, reverse/*, prescaled, factor*/) { + /** + * Calculates a linear value between 0.0 and 1.0 + * from a value and its lower and upper boundaries in hertz. + * + * @function TK.AudioMath#freq2coef + * + * @param {number} value - The value in hertz. + * @param {number} min - The minimum value in hertz. + * @param {number} max - The maximum value in hertz. + * @param {boolean} reverse - If the scale is reversed. + * + * @returns {number} A value between 0.0 (min) and 1.0 (max). + */ + value = +value; + min = +min; + max = +max; + reverse = reverse|0; + // FIXME: unused + if (reverse) value = max - (value - min); + min = +log10(min); + max = +log10(max); + value = ((+log10(value) - min) / (max - min)); + if (reverse) value = -value + 1.0; + return value; + } + function coef2freq(coef, min, max, reverse) { + /** + * Calculates a value in hertz from a value + * between 0.0 and 1.0 and some lower and upper boundaries in hertz. + * + * @function TK.AudioMath#coef2freq + * + * @param {number} coef - A value between 0.0 and 1.0. + * @param {number} min - The minimum value in hertz. + * @param {number} max - The maximum value in hertz. + * @param {boolean} reverse - If the scale is reversed. + * @param {number} factor - Changes the deflection of the logarithm if other than 1.0. + * + * @returns {number} The result in hertz. + */ + coef = +coef; + min = +min; + max = +max; + reverse = reverse|0; + if (reverse) coef = -coef + 1.0; + min = +log10(min); + max = +log10(max); + coef = +pow(10.0, (coef * (max - min) + min)); + if (reverse) coef = max - coef + min; + return coef; + } + function freq2scale(value, min, max, scale, reverse) { + /** + * Calculates a linear value between 0.0 and scale + * from a value and its lower and upper boundaries in hertz. + * + * @function TK.AudioMath#freq2scale + * + * @param {number} value - The value in hertz. + * @param {number} min - The minimum value in hertz. + * @param {number} max - The maximum value in hertz. + * @param {boolean} reverse - If the scale is reversed. + * + * @returns {number} A value between 0.0 and scale. + */ + value = +value; + min = +min; + max = +max; + scale = +scale; + reverse = reverse|0; + if (reverse) value = max - (value - min); + min = +log10(min); + max = +log10(max); + value = ((+log10(value) - min) / (max - min)); + if (reverse) value = -value + 1.0; + return value * scale; + } + function scale2freq(value, min, max, scale, reverse) { + /** + * Calculates a value in hertz from a value + * between 0.0 and scale and some lower and upper boundaries in hertz. + * + * @function TK.AudioMath#scale2freq + * + * @param {number} value - A value between 0.0 and scale. + * @param {number} min - The minimum value in hertz. + * @param {number} max - The maximum value in hertz. + * @param {boolean} reverse - If the scale is reversed. + * @param {number} factor - Changes the deflection of the logarithm if other than 1.0. + * + * @returns {number} The result in hertz. + */ + value = +value; + min = +min; + max = +max; + scale = +scale; + reverse = reverse|0; + value = value / scale; + if (reverse) value = -value + 1.0; + min = +log10(min); + max = +log10(max); + value = pow(10.0, (value * (max - min) + min)); + if (reverse) value = max - value + min; + return value; + } + + return { + // DECIBEL CALCULATIONS + db2coef: db2coef, + coef2db: coef2db, + db2scale: db2scale, + scale2db: scale2db, + gain2db: gain2db, + db2gain: db2gain, + // FREQUENCY CALCULATIONS + freq2coef: freq2coef, + coef2freq: coef2freq, + freq2scale: freq2scale, + scale2freq: scale2freq + } +})(); diff --git a/share/web_surfaces/builtin/mixer/toolkit/implements/base.js b/share/web_surfaces/builtin/mixer/toolkit/implements/base.js new file mode 100644 index 0000000000..83f7aa4ab2 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/implements/base.js @@ -0,0 +1,921 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function(w, TK) { +var merge = function(dst) { + var key, i, src; + for (i = 1; i < arguments.length; i++) { + src = arguments[i]; + for (key in src) { + dst[key] = src[key]; + } + } + return dst; +}; +var mixin = function(dst, src) { + var fun, key; + for (key in src) if (!dst[key]) { + if (key === "constructor" || + key === "_class" || + key === "Extends" || + key === "Implements" || + key === "options") continue; + if (!src.hasOwnProperty(key)) continue; + + fun = src[key]; + + dst[key] = fun; + } + + return dst; +}; +function call_handler(self, fun, args) { + try { + return fun.apply(self, args); + } catch (e) { + TK.warn("event handler", fun, "threw", e); + } +} +function dispatch_events(self, handlers, args) { + var v; + if (Array.isArray(handlers)) { + for (var i = 0; i < handlers.length; i++) { + v = call_handler(self, handlers[i], args); + if (v !== void(0)) return v; + } + } else return call_handler(self, handlers, args); +} +function add_event(to, event, fun) { + var tmp = to[event]; + + if (!tmp) { + to[event] = fun; + } else if (Array.isArray(tmp)) { + tmp.push(fun); + } else { + to[event] = [ tmp, fun ]; + } +} +function remove_event(from, event, fun) { + var tmp = from[event]; + if (!tmp) return; + if (Array.isArray(tmp)) { + for (var i = 0; i < tmp.length; i++) { + if (tmp[i] === fun) { + tmp[i] = tmp[tmp.length-1]; + tmp.length --; + } + } + if (tmp.length === 1) from[event] = tmp[0]; + else if (tmp.length === 0) delete from[event]; + } else if (tmp === fun) { + delete from[event]; + } +} +function add_static_event(w, event, fun) { + var p = w.prototype, e; + if (!p.hasOwnProperty('static_events')) { + if (p.static_events) { + p.static_events = e = Object.assign({}, p.static_events); + } else { + p.static_events = e = {}; + } + } else e = p.static_events; + add_event(e, event, fun); +} +function arrayify(x) { + if (!Array.isArray(x)) x = [ x ]; + return x; +} +function merge_static_events(a, b) { + var event; + if (!a) return b; + if (!b) return Object.assign({}, a); + for (event in a) { + var tmp = a[event]; + if (b.hasOwnProperty(event)) { + b[event] = arrayify(tmp).concat(arrayify(b[event])); + } else { + b[event] = Array.isArray(tmp) ? tmp.slice(0) : tmp; + } + } + return Object.assign({}, a, b); +} +TK.class = function(o) { + var constructor; + var methods; + var tmp, i, c, key; + + if (tmp = o.Extends) { + if (typeof(tmp) === "function") { + tmp = tmp.prototype; + } + if (typeof(o.options) === "object" && + typeof(tmp.options) === "object") { + o.options = Object.assign(Object.create(tmp.options), o.options); + } + if (o.static_events) o.static_events = merge_static_events(tmp.static_events, o.static_events); + methods = Object.assign(Object.create(tmp), o); + } else { + methods = o; + } + + tmp = o.Implements; + // mixins + if (tmp !== void(0)) { + tmp = arrayify(tmp); + for (i = 0; i < tmp.length; i++) { + if (typeof(tmp[i]) === "function") { + c = tmp[i].prototype; + } else c = tmp[i]; + + if (typeof(c.options) === "object") { + if (!methods.hasOwnProperty("options")) { + methods.options = Object.create(methods.options || null); + } + methods.options = merge({}, c.options, methods.options); + } + if (c.static_events) { + if (methods.static_events) { + methods.static_events = merge_static_events(c.static_events, + Object.assign({}, methods.static_events)); + } else { + methods.static_events = c.static_events; + } + } + + methods = mixin(methods, c, true); + } + } + + var init = methods.initialize; + var post_init = methods.initialized; + + if (post_init) { + constructor = function() { + init.apply(this, arguments); + post_init.call(this); + }; + } else constructor = init || (function() {}); + + constructor.prototype = methods; + methods.constructor = constructor; + return constructor; +}; +var __native_events = { + // mouse + mouseenter : true, + mouseleave : true, + mousedown : true, + mouseup : true, + mousemove : true, + mouseover : true, + + click : true, + dblclick : true, + + startdrag : true, + stopdrag : true, + drag : true, + dragenter : true, + dragleave : true, + dragover : true, + drop : true, + dragend : true, + + // touch + touchstart : true, + touchend : true, + touchmove : true, + touchenter : true, + touchleave : true, + touchcancel: true, + + keydown : true, + keypress : true, + keyup : true, + scroll : true, + focus : true, + blur : true, + + // mousewheel + mousewheel : true, + DOMMouseScroll : true, + wheel : true, + + submit : true, + contextmenu: true, +}; +function is_native_event(type) { + return __native_events[type]; +} + +function remove_native_events(element) { + var type; + var s = this.static_events; + var d = this.__events; + var handler = this.__native_handler; + + for (type in s) if (is_native_event(type)) + TK.remove_event_listener(element, type, handler); + + for (type in d) if (is_native_event(type) && (!s || !s.hasOwnProperty(type))) + TK.remove_event_listener(element, type, handler); +} +function add_native_events(element) { + var type; + var s = this.static_events; + var d = this.__events; + var handler = this.__native_handler; + + for (type in s) if (is_native_event(type)) + TK.add_event_listener(element, type, handler); + + for (type in d) if (is_native_event(type) && (!s || !s.hasOwnProperty(type))) + TK.add_event_listener(element, type, handler); +} +function native_handler(ev) { + /* FIXME: + * * mouseover and error are cancelled with true + * * beforeunload is cancelled with null + */ + if (this.fire_event(ev.type, ev) === false) return false; +} +function has_event_listeners(event) { + var ev = this.__events; + + if (ev.hasOwnProperty(event)) return true; + + ev = this.static_events; + + return ev && ev.hasOwnProperty(event); +} +/** + * This is the base class for all widgets in toolkit. + * It provides an API for event handling and other basic implementations. + * + * @class TK.Base + */ +TK.Base = TK.class({ + initialize : function(options) { + this.__events = {}; + this.__event_target = null; + this.__native_handler = native_handler.bind(this); + this.set_options(options); + this.fire_event("initialize"); + }, + initialized : function() { + /** + * Is fired when an instance is initialized. + * + * @event TK.Base#initialized + */ + this.fire_event("initialized"); + }, + /** + * Destroys all event handlers and the options object. + * + * @method TK.Base#destroy + */ + destroy : function() { + if (this.__event_target) { + remove_native_events.call(this, this.__event_target, this.__events); + } + + this.__events = null; + this.__event_target = null; + this.__native_handler = null; + this.options = null; + }, + /** + * Merges a new options object into the existing one, + * including deep copies of objects. If an option key begins with + * the string "on" it is considered as event handler. In this case + * the value should be the handler function for the event with + * the corresponding name without the first "on" characters. + * + * @method TK.Base#set_options(options) + * + * @param {Object} [options={ }] - An object containing initial options. + */ + set_options : function(o) { + var opt = this.options; + var key, a, b; + if (typeof(o) !== "object") { + delete this.options; + o = {}; + } else if (typeof(opt) === "object") for (key in o) if (o.hasOwnProperty(key)) { + a = o[key]; + b = opt[key]; + if (typeof a === "object" && a && + Object.getPrototypeOf(Object.getPrototypeOf(a)) === null && + typeof b === "object" && b && + Object.getPrototypeOf(Object.getPrototypeOf(b)) === null + ) { + o[key] = merge({}, b, a); + } + } + if (this.hasOwnProperty("options")) { + this.options = merge(opt, o); + } else if (opt) { + this.options = Object.assign(Object.create(opt), o); + } else { + this.options = Object.assign({}, o); + } + for (key in this.options) if (key.startsWith("on")) { + this.add_event(key.substr(2).toLowerCase(), this.options[key]); + delete this.options[key]; + } + }, + /** + * Get the value of an option. + * + * @method TK.Base#get + * + * @param {string} key - The option name. + */ + get: function (key) { + return this.options[key]; + }, + /** + * Sets an option. Fires both the events set with arguments key + * and value; and the event 'set_'+key with arguments value + * and key. + * + * @method TK.Base#set + * + * @param {string} key - The name of the option. + * @param {mixed} value - The value of the option. + * + * @emits TK.Base#set + * @emits TK.Base#set_[option] + */ + set: function (key, value) { + var e; + + this.options[key] = value; + /** + * Is fired when an option is set. + * + * @event TK.Base#set + * + * @param {string} name - The name of the option. + * @param {mixed} value - The value of the option. + */ + if (this.has_event_listeners("set")) + this.fire_event("set", key, value); + /** + * Is fired when an option is set. + * + * @event TK.Base#set_[option] + * + * @param {mixed} value - The value of the option. + */ + e = "set_"+key; + if (this.has_event_listeners(e)) + this.fire_event(e, value, key); + + return value; + }, + /** + * Sets an option by user interaction. Emits the userset + * event. The userset event can be cancelled (if an event handler + * returns false), in which case the option is not set. + * Returns true if the option was set, false + * otherwise. If the option was set, it will emit a useraction event. + * + * @method TK.Base#userset + * + * @param {string} key - The name of the option. + * @param {mixed} value - The value of the option. + * + * @emits TK.Base#userset + * @emits TK.Base#useraction + */ + userset: function(key, value) { + if (false === this.fire_event("userset", key, value)) return false; + value = this.set(key, value); + this.fire_event("useraction", key, value); + return true; + }, + /** + * Delegates all occuring DOM events of a specific DOM node to the widget. + * This way the widget fires e.g. a click event if someone clicks on the + * given DOM node. + * + * @method TK.Base#delegate_events + * + * @param {HTMLElement} element - The element all native events of the widget should be bound to. + * + * @returns {HTMLElement} The element + * + * @emits TK.Base#delegated + */ + delegate_events: function (element) { + var old_target = this.__event_target; + /** + * Is fired when an element is delegated. + * + * @event TK.Base#delegated + * + * @param {HTMLElement|Array} element - The element which receives all + * native DOM events. + * @param {HTMLElement|Array} old_element - The element which previously + * received all native DOM events. + */ + this.fire_event("delegated", element, old_target); + + if (old_target) remove_native_events.call(this, old_target); + if (element) add_native_events.call(this, element); + + this.__event_target = element; + + return element; + }, + /** + * Register an event handler. + * + * @method TK.Base#add_event + * + * @param {string} event - The event descriptor. + * @param {Function} func - The function to call when the event happens. + * @param {boolean} prevent - Set to true if the event should prevent the default behavior. + * @param {boolean} stop - Set to true if the event should stop bubbling up the tree. + */ + add_event: function (event, func) { + var ev, tmp; + + if (typeof event !== "string") + throw new TypeError("Expected string."); + + if (typeof func !== "function") + throw new TypeError("Expected function."); + + if (arguments.length !== 2) + throw new Error("Bad number of arguments."); + + if (is_native_event(event) && (ev = this.__event_target) && !this.has_event_listeners(event)) + TK.add_event_listener(ev, event, this.__native_handler); + ev = this.__events; + add_event(ev, event, func); + }, + /** + * Removes the given function from the event queue. + * If it is a native DOM event, it removes the DOM event listener + * as well. + * + * @method TK.Base#remove_event + * + * @param {string} event - The event descriptor. + * @param {Function} fun - The function to remove. + */ + remove_event: function (event, fun) { + remove_event(this.__events, event, fun); + + // remove native DOM event listener from __event_target + if (is_native_event(event) && !this.has_event_listeners(event)) { + var ev = this.__event_target; + if (ev) TK.remove_event_listener(ev, event, this.__native_handler); + } + }, + /** + * Fires an event. + * + * @method TK.Base#fire_event + * + * @param {string} event - The event descriptor. + * @param {...*} args - Event arguments. + */ + fire_event: function (event) { + var ev; + var args; + var v; + + ev = this.__events; + + if (ev !== void(0) && (event in ev)) { + ev = ev[event]; + + args = Array.prototype.slice.call(arguments, 1); + + v = dispatch_events(this, ev, args); + if (v !== void(0)) return v; + } + + ev = this.static_events; + + if (ev !== void(0) && (event in ev)) { + ev = ev[event]; + + if (args === void(0)) args = Array.prototype.slice.call(arguments, 1); + + v = dispatch_events(this, ev, args); + if (v !== void(0)) return v; + } + }, + /** + * Test if the event descriptor has some handler functions in the queue. + * + * @method TK.Base#has_event_listeners + * + * @param {string} event - The event desriptor. + * + * @returns {boolean} True if the event has some handler functions in the queue, false if not. + */ + has_event_listeners: has_event_listeners, + /** + * Add multiple event handlers at once, either as dedicated event handlers or a list of event + * descriptors with a single handler function. + * + * @method TK.Base#add_events + * + * @param {Object | Array} events - Object with event descriptors as keys and functions as + * values or Array of event descriptors. The latter requires a handler function as the + * second argument. + * @param {Function} func - A function to add as event handler if the first argument is an + * array of event desriptors. + */ + add_events: function (events, func) { + var i; + if (Array.isArray(events)) { + for (i = 0; i < events.length; i++) + this.add_event(events[i], func); + } else { + for (i in events) + if (events.hasOwnProperty(i)) + this.add_event(i, events[i]); + } + }, + /** + * Remove multiple event handlers at once, either as dedicated event handlers or a list of + * event descriptors with a single handler function. + * + * @method TK.Base#remove_events + * + * @param {Object | Array} events - Object with event descriptors as keys and functions as + * values or Array of event descriptors. The latter requires the handler function as the + * second argument. + * @param {Function} func - A function to remove from event handler queue if the first + * argument is an array of event desriptors. + */ + remove_events: function (events, func) { + var i; + if (Array.isArray(events)) { + for (i = 0; i < events.length; i++) + this.remove_event(events[i], func); + } else { + for (i in events) + if (events.hasOwnProperty(i)) + this.remove_event(i, events[i]); + } + }, + /** + * Fires several events. + * + * @method TK.Base#fire_events + * + * @param {Array.} events - A list of event names to fire. + */ + fire_events: function (events) { + for (var i in events) { + if (events.hasOwnProperty(i)) + this.fire_event(i, events[i]); + } + } +}); +function get_child_options(parent, name, options, config) { + var ret = {}; + var key, pref = name+"."; + var tmp; + + var inherit_options = !!config.inherit_options; + var blacklist_options = config.blacklist_options || []; + + if (tmp = config.default_options) + Object.assign(ret, (typeof(tmp) === "function") ? tmp.call(parent) : tmp); + + for (key in options) { + if (key.startsWith(pref)) { + ret[key.substr(pref.length)] = options[key]; + } + + if (inherit_options && blacklist_options.indexOf(tmp) < 0) { + if (key in config.create.prototype._options && !(key in TK["Widget"].prototype._options)) { + ret[key] = options[key]; + } + } + } + + var map_options = config.map_options; + + if (map_options) for (key in map_options) { + if (options[key]) { + ret[map_options[key]] = options[key]; + } + } + + return ret; +} +function ChildWidget(widget, name, config) { + + /** + * Defines a {@link TK.Widget} as a child for another widget. This function + * is used internally to simplify widget definitions. E.g. the {@link TK.Icon} of a + * {@link TK.Button} is defined as a TK.ChildWidget. TK.ChildWidgets + * are created/added after the initialization of the parent widget. + * If not configured explicitly, all options of the child widget can + * be accessed via TK.Widget.options[config.name + "." + option] + * on the parent widget. + * + * @param {TK.Widget} widget - The {@link TK.Widget} to add the TK.ChildWidget to. + * @param {string} name - The identifier of the element inside the parent Element, TK.Widget[config.name]. + * @param {object} config - The configuration of the child element. + * + * @property {TK.Widget} config.create - A TK.Widget class derivate to be used as child widget. + * @property {boolean} [config.fixed] - A fixed child widget cannot be removed after initialization. + * @property {boolean} [config.show=false] - Show/hide a non-fixed child widget on initialization. + * @property {string} [config.option="show_"+config.name] - A custom option of the parent widget + * to determine the visibility of the child element. If this is + * null, TK.Widget.options["show_"+ config.name] + * is used to toggle its visibility. The child element is visible, if + * this options is !== false. + * @property {function} [config.append] - A function overriding the generic + * append mechanism. If not null, this function is + * supposed to take care of adding the child widget to the parent + * widgets DOM. Otherwise the element of the child widget is added + * to the element of the parent widget. + * @property {boolean} [config.inherit_options=false] - Defines if both widgets share the + * same set of options. If true, Setting an option on the + * parent widget also sets the same option on the child widget. If false, + * the options of the child widget can be accessed via options[config.name + "." + option] + * in the parent widget. + * @property {array} [config.map_options=[]] - A list of options to be mapped between + * parent and child. Keys are the options to be added to the parent, values the options + * names in the child widget. + * names in the parent to which the childrens options (defined as values) are mapped to. + * If one of these options is set + * on the parent widget, it also gets set on the child widget. This is + * a fine-grained version of config.inherit-options. + * @property {boolean} [config.userset_ignore=false] - Do not care about the userset + * event of the parent widget, only keep track of set. + * @property {boolean} [config.userset_delegate=false] - Delegates all user interaction from + * the child to the parent element. If the user triggers an event on + * the child widget, the userset function of the parent + * element is called. + * @property {array} [config.static_events=[]] - An array of static events to be + * added to the parent widget. Each entry is a mapping between + * the name of the event and the callback function. + * @property {boolean} [config.toggle_class=false] - Defines if the parent widget + * receives the class toolkit-has-[name] as soon as + * the child element is shown. + * @property {array} [config.blacklist_options] - Array containing options names + * which are skipped on `inherit_options`. + * + * @class TK.ChildWidget + * + */ + + var p = widget.prototype; + var key = config.option || "show_"+name; + var tmp, m; + var static_events = { }; + + if (!config.userset_ignore) + static_events.userset = (config.inherit_options || config.userset_delegate) + ? function(key, value) { this.parent.userset(key, value); return false; } + : function(key, value) { this.parent.userset(name+"."+key, value); return false; }; + + + if (m = config.static_events) + Object.assign(static_events, m); + + if (config.create === void(0)) { + TK.warn("'create' is undefined. Skipping ChildWidget ", name); + return; + } + + var child = TK.class({ + Extends: config.create, + static_events: static_events, + }); + + + /* trigger child widget creation after initialization */ + add_static_event(widget, "initialized", function() { + /* we do not want to trash the class cache */ + if (!this[name]) + { + this[name] = null; + this.set(key, this.options[key]); + } + }); + + /* clean up on destroy */ + add_static_event(widget, "destroy", function() { + if (this[name]) { + this[name].destroy(); + this[name] = null; + } + }); + + var fixed = config.fixed; + var append = config.append; + var toggle_class = !!config.toggle_class; + + if (append === void(0)) append = true; + + /* child widget creation */ + add_static_event(widget, "set_"+key, function(val) { + var C = this[name]; + var show = fixed || !!val; + if (show && !C) { + var O = get_child_options(this, name, this.options, config); + if (append === true) + O.container = this.element; + var w = new child(O); + this.add_child(w); + this[name] = w; + if (typeof(append) === "function") + append.call(this); + } else if (!show && C) { + C.destroy(); + this[name] = null; + } + if (toggle_class) TK.toggle_class(this.element, "toolkit-has-"+name, show); + this.trigger_resize(); + }); + var set_cb = function(val, key) { + if (this[name]) this[name].set(key.substr(name.length+1), val); + }; + + for (tmp in child.prototype._options) { + add_static_event(widget, "set_"+name+"."+tmp, set_cb); + p._options[name+"."+tmp] = child.prototype._options[tmp]; + } + + /* direct option inherit */ + var blacklist_options = config.blacklist_options || []; + if (config.inherit_options) { + set_cb = function(val, key) { + if (this[name]) this[name].set(key, val); + }; + for (tmp in child.prototype._options) { + if (tmp in TK["Widget"].prototype._options) continue; + if (blacklist_options.indexOf(tmp) > -1) continue; + add_static_event(widget, "set_"+tmp, set_cb); + if (!p._options[tmp]) + p._options[tmp] = child.prototype._options[tmp]; + } + } + set_cb = function(key) { + return function(val) { + if (this[name]) this[name].set(key, val); + }; + }; + if (m = config.map_options) { + for (tmp in m) { + p._options[tmp] = child.prototype._options[m[tmp]]; + if (!p.options[tmp]) + p.options[tmp] = child.prototype.options[m[tmp]]; + add_static_event(widget, "set_"+tmp, set_cb(m[tmp])); + } + } + if (!config.options) { + if (!p._options[key]) + p._options[key] = "boolean"; + p.options[key] = fixed || !!config.show; + } +} +TK.add_static_event = add_static_event; +TK.ChildWidget = ChildWidget; + +function ChildElement(widget, name, config) { + /** + * Creates a HTMLElement as a child for a widget. Is used to simplify + * widget definitions. E.g. the tiny marker used to display the back-end + * value is a simple DIV added using TK.ChildElement. The generic element + * is a DIV added to TK.Widget.element with the class + * toolkit-[name]. Default creating and adding can be + * overwritten with custom callback functions. + * + * @param {TK.Widget} widget - The {@link TK.Widget} to add the TK.ChildElement to. + * @param {string} name - The identifier of the element. It will be prefixed + * by an underscore TK.Widget["_" + config.name]. + * @param {object} config - The configuration of the child element. + * + * @property {boolean} [config.show=false] - Show/hide the child element on initialization. + * @property {string} [config.option="show_"+config.name] - A custom option of the parent widget + * to determine the visibility of the child element. If this is + * null, TK.Widget.options["show_"+ config.name] + * is used to toggle its visibility. The child element is visible, if + * this options is !== false. + * @property {function} [config.display_check] - A function overriding the + * generic show_option behavior. If set, this function + * is called with the value of show_option as argument + * as soon as it gets set and is supposed to return a boolean + * defining the visibility of the element. + * @property {function} [config.append] - A function overriding the generic + * append mechanism. If not null, this function is + * supposed to take care of adding the child element to the parent + * widgets DOM. + * @property {function} [config.create] - A function overriding the generic + * creation mechanism. If not null, this function is + * supposed to create and return a DOM element to be added to the + * parent widget. + * @property {boolean} [config.toggle_class=false] - Defines if the parent widget + * receives the class toolkit-has-[name] as soon as + * the child element is shown. + * @property {array} [config.draw_options] - A list of options of the parent + * widget which are supposed to trigger a check if the element has to + * be added or removed. + * @property {function} [config.draw] - A function to be called on redraw. + * + * @class TK.ChildElement + * + */ + var p = widget.prototype; + var show_option = config.option || ("show_" + name); + var index = "_"+name; + + var display_check = config.display_check; + + /* This is done to make sure that the object property is created + * inside of the constructor. Otherwise, if we add the widget later + * might be turned into a generic mapping. + */ + add_static_event(widget, "initialize", function() { + this[index] = null; + }); + + /* trigger child element creation after initialization */ + add_static_event(widget, "initialized", function() { + this.set(show_option, this.options[show_option]); + }); + + /* clean up on destroy */ + add_static_event(widget, "destroy", function() { + if (this[index]) { + this[index].remove(); + this[index] = null; + } + }); + + var append = config.append; + var create = config.create; + var toggle_class = !!config.toggle_class; + + if (create === void(0)) create = function() { return TK.element("div", "toolkit-"+name); } + if (append === void(0)) append = function() { this.element.appendChild(this[index]); } + + add_static_event(widget, "set_"+show_option, function(value) { + var C = this[index]; + var show = display_check ? display_check(value) : (value !== false); + if (show && !C) { + C = create.call(this); + this[index] = C; + append.call(this, this.options); + } else if (C && !show) { + this[index] = null; + C.remove(); + } + if (toggle_class) TK.toggle_class(this.element, "toolkit-has-"+name, value); + this.trigger_resize(); + }); + + if (config.draw) { + var m = config.draw_options; + + if (!m) m = [ show_option ]; + else m.push(show_option); + + for (var i = 0; i < m.length; i++) { + add_static_event(widget, "set_"+m[i], function() { + if (this.options[show_option] !== false) + this.draw_once(config.draw); + }); + } + } + + if (p._options[show_option] === void(0)) { + p._options[show_option] = "boolean"; + p.options[show_option] = !!config.show; + } +} +TK.ChildElement = ChildElement; +})(this, this.TK||{}); diff --git a/share/web_surfaces/builtin/mixer/toolkit/implements/globalcursor.js b/share/web_surfaces/builtin/mixer/toolkit/implements/globalcursor.js new file mode 100644 index 0000000000..b54c0076c9 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/implements/globalcursor.js @@ -0,0 +1,71 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function(w, TK){ +/** + * TK.GlobalCursor adds global cursor classes to ensure + * one of the standard cursors + * is shown in the overall application. + * + * @mixin TK.GlobalCursor + */ +TK.GlobalCursor = TK.class({ + _class: "GlobalCursor", + /** + * Adds a class "toolkit-cursor-" + cursor to the document.body to show a specific cursor. + * + * @method TK.GlobalCursor#global_cursor + * + * @param {string} cursor - The name of the cursor to show. + * + * @emits TK.GlobalCursor#globalcursor + */ + global_cursor: function (cursor) { + TK.add_class(document.body, "toolkit-cursor-" + cursor); + /** + * Is fired when a cursor gets set. + * + * @event TK.GlobalCursor#globalcursor + * + * @param {string} cursor - The name of the cursor to show. + */ + this.fire_event("globalcursor", cursor); + }, + /** + * Removes the class from document.body node. + * + * @method TK.GlobalCursor#remove_cursor + * + * @param {string} cursor - The name of the cursor to remome. + * + * @emits TK.GlobalCursor#cursorremoved + */ + remove_cursor: function (cursor) { + TK.remove_class(document.body, "toolkit-cursor-" + cursor); + /** + * Is fired when a cursor is removed. + * + * @event TK.GlobalCursor#cursorremoved + * + * @param {string} cursor - The name of the cursor to remove. + */ + this.fire_event("cursorremoved", cursor); + } +}); +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/implements/gradient.js b/share/web_surfaces/builtin/mixer/toolkit/implements/gradient.js new file mode 100644 index 0000000000..8cda82827d --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/implements/gradient.js @@ -0,0 +1,211 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function(w, TK){ +TK.Gradient = TK.class({ + /** + * TK.Gradient provides a function to set the background of a + * DOM element to a CSS gradient according on the users browser and version. + * TK.Gradient needs a {@link TK.Range} to be implemented on. + * + * @mixin TK.Gradient + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {Object|Boolean} options.gradient - Gradient definition for the background. + * Keys are ints or floats as string corresponding to the widgets scale. + * Values are valid css color strings like #ff8000 or rgb(0,56,103). + * If set to false the css style color is used. + * @property {String|Boolean} [options.background="#000000"] - Background color if no gradient is used. + * Values are valid css color strings like #ff8000 or rgb(0,56,103). + * If set to false the css style color is used. + */ + _class: "Gradient", + Implements: TK.Ranged, + _options: Object.assign(TK.Ranged.prototype._options, { + gradient: "mixed", + background: "mixed", + }), + options: { + gradient: false, + background: false + }, + draw_gradient: function (element, gradient, fallback, range) { + /** + * This function generates a string from a given + * gradient object to set as a CSS background for a DOM element. + * If element is given, the function automatically sets the + * background. If gradient is omitted, the gradient is taken from + * options. Fallback is used if no gradient can be created. + * If fallback is omitted, options.background is used. if no range + * is set Gradient assumes that the implementing instance has + * {@link TK.Range} functionality. + * + * @method TK.Gradient#draw_gradient + * + * @param {DOMNode} element - The DOM node to apply the gradient to. + * @param {Object} [gradient=this.options.gradient] - Gradient definition for the background, e.g. {"-96": "rgb(30,87,153)", "-0.001": "rgb(41,137,216)", "0": "rgb(32,124,202)", "24": "rgb(125,185,232)"}. + * @param {string} [fallback=this.options.background] - If no gradient can be applied, use this as background color string. + * @param {TK.Range} [range=this] - If a specific range is set, it is used for the calculations. If not, we expect the widget itself provides {@link TK.Ranged} functionality. + * + * @returns {string} A string to be used as background CSS. + * + * @mixes TK.Ranged + * + * @emits TK.Gradient#backgroundchanged + */ + + // {"-96": "rgb(30,87,153)", "-0.001": "rgb(41,137,216)", "0": "rgb(32,124,202)", "24": "rgb(125,185,232)"} + // becomes: +// background: url("data:image/svg+xml;utf8, "); +// background: -moz-linear-gradient(top, rgb(30,87,153) 0%, rgb(41,137,216) 50%, rgb(32,124,202) 51%, rgb(125,185,232) 100%); +// background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgb(30,87,153)), color-stop(50%,rgb(41,137,216)), color-stop(51%,rgb(32,124,202)), color-stop(100%,rgb(125,185,232))); +// background: -webkit-linear-gradient(top, rgb(30,87,153) 0%,rgb(41,137,216) 50%,rgb(32,124,202) 51%,rgb(125,185,232) 100%); +// background: -o-linear-gradient(top, rgb(30,87,153) 0%,rgb(41,137,216) 50%,rgb(32,124,202) 51%,rgb(125,185,232) 100%); +// background: -ms-linear-gradient(top, rgb(30,87,153) 0%,rgb(41,137,216) 50%,rgb(32,124,202) 51%,rgb(125,185,232) 100%); +// background: linear-gradient(to bottom, rgb(30,87,153) 0%,rgb(41,137,216) 50%,rgb(32,124,202) 51%,rgb(125,185,232) 100%); +// filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#1e5799', endColorstr='#7db9e8',GradientType=0 ); + + var bg = ""; + range = range || this; + if (!gradient && !this.options.gradient) + bg = fallback || this.options.background; + else { + gradient = gradient || this.options.gradient; + + var ms_first = ""; + var ms_last = ""; + var m_svg = ""; + var m_regular = ""; + var m_webkit = ""; + var s_ms = "background filter: progid:DXImageTransform." + + "Microsoft.gradient( startColorstr='%s', " + + "endColorstr='%s',GradientType='%d' )"; + var s_svg = "url(\"data:image/svg+xml;utf8," + + "%s" + + "\")"; + var s_regular = "%slinear-gradient(%s, %s)"; + var s_webkit = "-webkit-gradient(linear, %s, %s)"; + var c_svg = ""; + var c_regular = "%s %s%%, "; + var c_webkit = "color-stop(%s%%, %s), "; + + var d_w3c = {}; + d_w3c["s"+"left"] = "to top"; + d_w3c["s"+"right"] = "to top"; + d_w3c["s"+"top"] = "to right"; + d_w3c["s"+"bottom"] = "to right"; + + var d_regular = {}; + d_regular["s"+"left"] = "bottom"; + d_regular["s"+"right"] = "bottom"; + d_regular["s"+"top"] = "left"; + d_regular["s"+"bottom"] = "left"; + + var d_webkit = {}; + d_webkit["s"+"left"] = "left bottom, left top"; + d_webkit["s"+"right"] = "left bottom, left top"; + d_webkit["s"+"top"] = "left top, right top"; + d_webkit["s"+"bottom"] = "left top, right top"; + + var d_ms = {}; + d_ms["s"+"left"] = 'x1="0%" y1="0%" x2="0%" y2="100%"'; + d_ms["s"+"right"] = 'x1="0%" y1="0%" x2="0%" y2="100%"'; + d_ms["s"+"top"] = 'x1="100%" y1="0%" x2="0%" y2="0%"'; + d_ms["s"+"bottom"] = 'x1="100%" y1="0%" x2="0%" y2="0%"'; + + var keys = Object.keys(gradient); + for (var i = 0; i < keys.length; i++) { + keys[i] = parseFloat(keys[i]); + } + keys = keys.sort(this.options.reverse ? + function (a,b) { return b-a } : function (a,b) { return a-b }); + + for (var i = 0; i < keys.length; i++) { + var ps = (100*range.val2coef(range.snap(keys[i]))).toFixed(2); + if (!ms_first) ms_first = gradient[i]; + ms_last = gradient[keys[i] + ""]; + + m_svg += TK.sprintf(c_svg, ps, gradient[keys[i] + ""]); + m_regular += TK.sprintf(c_regular, gradient[keys[i] + ""], ps); + m_webkit += TK.sprintf(c_webkit, ps, gradient[keys[i] + ""]); + } + m_regular = m_regular.substr(0, m_regular.length -2); + m_webkit = m_regular.substr(0, m_webkit.length -2); + + if (TK.browser.name === "IE" && TK.browser.version <= 8) + bg = (TK.sprintf(s_ms, ms_last, ms_first, this._vert() ? 0:1)); + + else if (TK.browser.name === "IE" && TK.browser.version === 9) + bg = (TK.sprintf(s_svg, this.options.id, + d_ms["s"+this.options.layout], + m_svg, this.options.id)); + + else if (TK.browser.name === "IE" && TK.browser.version >= 10) + bg = (TK.sprintf(s_regular, "-ms-", + d_regular["s" + this.options.layout], + m_regular)); + + else if (TK.browser.name=="Firefox") + bg = (TK.sprintf(s_regular, "-moz-", + d_regular["s"+this.options.layout], + m_regular)); + + else if (TK.browser.name === "Opera" && TK.browser.version >= 11) + bg = (TK.sprintf(s_regular, "-o-", + d_regular["s"+this.options.layout], + m_regular)); + + else if (TK.browser.name === "Chrome" && TK.browser.version < 10 + || TK.browser.name === "Safari" && TK.browser.version < 5.1) + bg = (TK.sprintf(s_webkit, + d_webkit["s"+this.options.layout], + m_regular)); + + else if (TK.browser.name === "Chrome" || TK.browser.name === "Safari") + bg = (TK.sprintf(s_regular, "-webkit-", + d_regular["s"+this.options.layout], + m_regular)); + + else + bg = (TK.sprintf(s_regular, "", + d_w3c["s"+this.options.layout], + m_regular)); + } + + if (element) { + element.style.background = bg ? bg : void 0; + /** + * Is fired when the gradient was created. + * + * @event TK.Gradient#backgroundchanged + * + * @param {HTMLElement} element - The element which background has changed. + * @param {string} background - The background of the element as CSS color string. + */ + this.fire_event("backgroundchanged", element, bg); + } + return bg; + } +}); +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/implements/notes.js b/share/web_surfaces/builtin/mixer/toolkit/implements/notes.js new file mode 100644 index 0000000000..0740de8c8c --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/implements/notes.js @@ -0,0 +1,102 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function(w, TK){ +var notes = [ "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" ]; +TK.Notes = TK.class({ + /** + * TK.Notes converts between frequencies, MIDI notes + * and note names. + * + * @mixin TK.Notes + */ + _class: "Notes", + /** + * Returns a note name for a MIDI note number. + * + * @method TK.Notes#midi2note + * + * @param {int} note - The MIDI note to translate. + * + * @returns {string} note - The name of the note. + */ + midi2note: function (num) { + return notes[num % 12] + parseInt(num / 12); + }, + /** + * Returns a frequency of a MIDI note number. + * + * @method TK.Notes#midi2freq + * + * @param {int} note - The MIDI note to translate. + * + * @returns {number} frequency - The frequency of the MIDI number. + */ + midi2freq: function (num, base) { + base |= 440; + return Math.pow(2, (num - 69) / 12) * base; + }, + /** + * Returns a MIDI note number for a frequency. + * + * @method TK.Notes#freq2midi + * + * @param {number} frequency - The frequency to translate. + * @param {number} [base] - The frequency of A440. + * + * @returns {int} number - The MIDI number of the frequency. + */ + freq2midi: function (freq, base) { + base |= 440; + var f2 = Math.log2(freq / base); + return Math.max(0, Math.round(12 * f2 + 69)); + }, + /** + * Returns the percents a frequency misses a real note by. + * + * @method TK.Notes#freq2cents + * + * @param {number} frequency - The frequency to translate. + * @param {number} [base] - The frequency of A440. + * + * @returns {number} cents - The percent of the difference to the next full note. + */ + freq2cents: function (freq, base) { + base |= 440; + var f2 = Math.log2(freq / base); + f2 *= 1200; + f2 %= 100; + return (f2 < -50) ? 100 + f2 : (f2 > 50) ? -(100 - f2) : f2; + }, + /** + * Returns a note name for a frequency. + * + * @method TK.Notes#freq2note + * + * @param {number} frequency - The frequency to translate. + * @param {number} [base] - The frequency of A440. + * + * @returns {string} note - The name of the note. + */ + freq2note: function (freq, base) { + base |= 440; + return this.midi2note(this.freq2midi(freq, base)); + } +}); +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/implements/ranged.js b/share/web_surfaces/builtin/mixer/toolkit/implements/ranged.js new file mode 100644 index 0000000000..f75133bd51 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/implements/ranged.js @@ -0,0 +1,664 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function(w, TK){ +function LinearSnapModule(stdlib, foreign) { + var min = +foreign.min; + var max = +foreign.max; + var step = +foreign.step; + var base = +foreign.base; + + var floor = stdlib.Math.floor; + var ceil = stdlib.Math.ceil; + + function low_snap(v, direction) { + v = +v; + direction = +direction; + var n = 0.0; + var t = 0.0; + + if (!(v > min)) { + v = min; + direction = 1.0; + } else if (!(v < max)) { + v = max; + direction = +1.0; + } + + t = (v - base)/step; + + if (direction > 0.0) n = ceil(t); + else if (direction < 0.0) n = floor(t); + else { + if (t - floor(t) < 0.5) { + n = floor(t); + } else { + n = ceil(t); + } + } + + return base + step * n; + } + + /** + * Returns the nearest value on the grid which is bigger than value. + * + * @method TK.Ranged#snap_up + * + * @param {number} value - The value to snap. + * + * @returns {number} The snapped value. + */ + function snap_up(v) { + v = +v; + return +low_snap(v, 1.0); + } + + /** + * Returns the nearest value on the grid which is smaller than value. + * + * @method TK.Ranged#snap_down + * + * @param {number} value - The value to snap. + * + * @returns {number} The snapped value. + */ + function snap_down(v) { + v = +v; + return +low_snap(v, -1.0); + } + + /** + * Returns the nearest value on the grid. Its rounding behavior is similar to that + * of Math.round. + * + * @method TK.Ranged#snap + * + * @param {number} value - The value to snap. + * + * @returns {number} The snapped value. + */ + function snap(v) { + v = +v; + return +low_snap(v, 0.0); + } + + return { + snap_up : snap_up, + snap_down : snap_down, + snap : snap + }; +} + +function ArraySnapModule(stdlib, foreign, heap) { + var values = new stdlib.Float64Array(heap); + var len = (heap.byteLength>>3)|0; + var min = +(foreign.min !== void 0 ? foreign.min : values[0]); + var max = +(foreign.max !== void 0 ? foreign.max : values[len-1]); + + function low_snap(v, direction) { + v = +v; + direction = +direction; + var a = 0; + var mid = 0; + var b = 0; + var t = 0.0; + + b = len-1; + + if (!(v > min)) v = min; + if (!(v < max)) v = max; + + if (!(v < +values[b << 3 >> 3])) return +values[b << 3 >> 3]; + if (!(v > +values[0])) return +values[0]; + + do { + mid = (a + b) >>> 1; + t = +values[mid << 3 >> 3]; + if (v > t) a = mid; + else if (v < t) b = mid; + else return t; + } while (((b - a)|0) > 1); + + if (direction > 0.0) return +values[b << 3 >> 3]; + else if (direction < 0.0) return +values[a << 3 >> 3]; + + if (values[b << 3 >> 3] - v <= v - values[a << 3 >> 3]) return +values[b << 3 >> 3]; + return +values[a << 3 >> 3]; + } + + function snap_up(v) { + v = +v; + return +low_snap(v, 1.0); + } + + function snap_down(v) { + v = +v; + return +low_snap(v, -1.0); + } + + function snap(v) { + v = +v; + return +low_snap(v, 0.0); + } + + return { + snap_up : snap_up, + snap_down : snap_down, + snap : snap + }; +} +function NullSnapModule(stdlib, foreign, heap) { + var min = +foreign.min; + var max = +foreign.max; + + function snap(v) { + v = +v; + if (!(v < max)) v = max; + if (!(v > min)) v = min; + return v; + } + + return { + snap: snap, + snap_up: snap, + snap_down: snap, + }; +} +function num_sort(a) { + a = a.slice(0); + a.sort(function(a, b) { return a - b; }); + return a; +} +function update_snap() { + var O = this.options; + // Notify that the ranged options have been modified + if (Array.isArray(O.snap)) { + Object.assign(this, ArraySnapModule(window, O, new Float64Array(num_sort(O.snap)).buffer)); + } else if (typeof O.snap === "number" && O.snap > 0.0) { + Object.assign(this, LinearSnapModule(window, { min : Math.min(O.min, O.max), max : Math.max(O.min, O.max), step : O.snap, base: O.base||0 })); + } else if (O.min < Infinity && O.max > -Infinity) { + Object.assign(this, NullSnapModule(window, { min : Math.min(O.min, O.max), max : Math.max(O.min, O.max) })); + } else { + Object.assign(this, { + snap: function(v) { return +v; }, + snap_up: function(v) { return +v; }, + snap_down: function(v) { return +v; }, + }); + } +} +function TRAFO_PIECEWISE(stdlib, foreign, heap) { + var reverse = foreign.reverse|0; + var l = heap.byteLength >> 4; + var X = new Float64Array(heap, 0, l); + var Y = new Float64Array(heap, l*8, l); + var basis = +foreign.basis; + + function val2based(coef, size) { + var a = 0, + b = (l-1)|0, + mid = 0, + t = 0.0; + + coef = +coef; + size = +size; + + if (!(coef > +Y[0])) return +X[0] * size; + if (!(coef < +Y[b << 3 >> 3])) return +X[b << 3 >> 3] * size; + + do { + mid = (a + b) >>> 1; + t = +Y[mid << 3 >> 3]; + if (coef > t) a = mid; + else if (coef < t) b = mid; + else return +X[mid << 3 >> 3] * size; + } while (((b - a)|0) > 1); + + /* value lies between a and b */ + + t = (+X[b << 3 >> 3] - +X[a << 3 >> 3]) / (+Y[b << 3 >> 3] - +Y[a << 3 >> 3]); + + t = +X[a << 3 >> 3] + (coef - +Y[a << 3 >> 3]) * t; + + t *= size; + + if (reverse) t = size - t; + + return t; + } + function based2val(coef, size) { + var a = 0, + b = (l-1)|0, + mid = 0, + t = 0.0; + + coef = +coef; + size = +size; + if (reverse) coef = size - coef; + coef /= size; + + if (!(coef > 0)) return Y[0]; + if (!(coef < 1)) return Y[b << 3 >> 3]; + + do { + mid = (a + b) >>> 1; + t = +X[mid << 3 >> 3]; + if (coef > t) a = mid; + else if (coef < t) b = mid; + else return +Y[mid << 3 >> 3]; + } while (((b - a)|0) > 1); + + /* value lies between a and b */ + + t = (+Y[b << 3 >> 3] - +Y[a << 3 >> 3]) / (+X[b << 3 >> 3] - +X[a << 3 >> 3]); + + return +Y[a << 3 >> 3] + (coef - +X[a << 3 >> 3]) * t; + } + function val2px(n) { return val2based(n, basis || 1); } + function px2val(n) { return based2val(n, basis || 1); } + function val2coef(n) { return val2based(n, 1); } + function coef2val(n) { return based2val(n, 1); } + return { + val2based:val2based, + based2val:based2val, + val2px:val2px, + px2val:px2val, + val2coef:val2coef, + coef2val:coef2val, + }; +}; +function TRAFO_FUNCTION(stdlib, foreign) { + var reverse = foreign.reverse|0; + var min = +foreign.min; + var max = +foreign.max; + var scale = foreign.scale; + var basis = +foreign.basis; + function val2based(value, size) { + value = +value; + size = +size; + value = scale(value, foreign, false) * size; + if (reverse) value = size - value; + return value; + } + function based2val(coef, size) { + coef = +coef; + size = +size; + if (reverse) coef = size - coef; + coef = scale(coef/size, foreign, true); + return coef; + } + function val2px(n) { return val2based(n, basis || 1); } + function px2val(n) { return based2val(n, basis || 1); } + function val2coef(n) { return val2based(n, 1); } + function coef2val(n) { return based2val(n, 1); } + return { + val2based:val2based, + based2val:based2val, + val2px:val2px, + px2val:px2val, + val2coef:val2coef, + coef2val:coef2val, + }; +} +function TRAFO_LINEAR(stdlib, foreign) { + var reverse = foreign.reverse|0; + var min = +foreign.min; + var max = +foreign.max; + var basis = +foreign.basis; + function val2based(value, size) { + value = +value; + size = +size; + value = ((value - min) / (max - min)) * size; + if (reverse) value = size - value; + return value; + } + function based2val(coef, size) { + coef = +coef; + size = +size; + if (reverse) coef = size - coef; + coef = (coef / size) * (max - min) + min; + return coef; + } + // just a wrapper for having understandable code and backward + // compatibility + function val2px(n) { n = +n; if (basis == 0.0) basis = 1.0; return +val2based(n, basis); } + // just a wrapper for having understandable code and backward + // compatibility + function px2val(n) { n = +n; if (basis == 0.0) basis = 1.0; return +based2val(n, basis); } + // calculates a coefficient for the value + function val2coef(n) { n = +n; return +val2based(n, 1.0); } + // calculates a value from a coefficient + function coef2val(n) { n = +n; return +based2val(n, 1.0); } + return { + /** + * Transforms a value from the coordinate system to the interval 0...basis. + * + * @method TK.Ranged#val2based + * + * @param {number} value + * @param {number} [basis=1] + * + * @returns {number} + */ + val2based:val2based, + /** + * Transforms a value from the interval 0...basis to the coordinate system. + * + * @method TK.Ranged#based2val + * + * @param {number} value + * @param {number} [basis=1] + * + * @returns {number} + */ + based2val:based2val, + /** + * This is an alias for {@link TK.Ranged#val2px}. + * + * @method TK.Ranged#val2px + * + * @param {number} value + * + * @returns {number} + */ + val2px:val2px, + /** + * This is an alias for {@link TK.Ranged#px2val}. + * + * @method TK.Ranged#px2val + * + * @param {number} value + * + * @returns {number} + */ + px2val:px2val, + /** + * Calls {@link based2val} with basis = 1. + * + * @method TK.Ranged#val2coef + * + * @param {number} value + * + * @returns {number} + */ + val2coef:val2coef, + /** + * Calls {@link based2val} with basis = 1. + * + * @method TK.Ranged#coef2val + * + * @param {number} value + * + * @returns {number} + */ + coef2val:coef2val, + }; +} +function TRAFO_LOG(stdlib, foreign) { + var db2scale = stdlib.TK.AudioMath.db2scale; + var scale2db = stdlib.TK.AudioMath.scale2db; + var reverse = foreign.reverse|0; + var min = +foreign.min; + var max = +foreign.max; + var log_factor = +foreign.log_factor; + var trafo_reverse = foreign.trafo_reverse|0; + var basis = +foreign.basis; + function val2based(value, size) { + value = +value; + size = +size; + value = +db2scale(value, min, max, size, trafo_reverse, log_factor); + if (reverse) value = size - value; + return value; + } + function based2val(coef, size) { + coef = +coef; + size = +size; + if (reverse) coef = size - coef; + coef = +scale2db(coef, min, max, size, trafo_reverse, log_factor); + return coef; + } + function val2px(n) { return val2based(n, basis || 1); } + function px2val(n) { return based2val(n, basis || 1); } + function val2coef(n) { return val2based(n, 1); } + function coef2val(n) { return based2val(n, 1); } + return { + val2based:val2based, + based2val:based2val, + val2px:val2px, + px2val:px2val, + val2coef:val2coef, + coef2val:coef2val, + }; +} +function TRAFO_FREQ(stdlib, foreign) { + var freq2scale = stdlib.TK.AudioMath.freq2scale; + var scale2freq = stdlib.TK.AudioMath.scale2freq; + var reverse = foreign.reverse|0; + var min = +foreign.min; + var max = +foreign.max; + var trafo_reverse = foreign.trafo_reverse|0; + var basis = +foreign.basis; + function val2based(value, size) { + value = +value; + size = +size; + value = +freq2scale(value, min, max, size, trafo_reverse); + if (reverse) value = size - value; + return value; + } + function based2val(coef, size) { + coef = +coef; + size = +size; + if (reverse) coef = size - coef; + coef = +scale2freq(coef, min, max, size, trafo_reverse); + return coef; + } + function val2px(n) { return val2based(n, basis || 1); } + function px2val(n) { return based2val(n, basis || 1); } + function val2coef(n) { return val2based(n, 1); } + function coef2val(n) { return based2val(n, 1); } + return { + val2based:val2based, + based2val:based2val, + val2px:val2px, + px2val:px2val, + val2coef:val2coef, + coef2val:coef2val, + }; +} +function update_transformation() { + var O = this.options; + var scale = O.scale; + + var module; + + if (typeof scale === "function") { + module = TRAFO_FUNCTION(w, O); + } else if (Array.isArray(scale)) { + var i = 0; + if (scale.length % 2) { + TK.error("Malformed piecewise-linear scale."); + } + + for (i = 0; i < scale.length/2 - 1; i++) { + if (!(scale[i] >= 0 && scale[i] <= 1)) + TK.error("piecewise-linear x value not in [0,1]."); + if (!(scale[i] < scale[i+1])) + TK.error("piecewise-linear array not sorted."); + } + for (i = scale.length/2; i < scale.length - 1; i++) { + if (!(scale[i] < scale[i+1])) + TK.error("piecewise-linear array not sorted."); + } + + module = TRAFO_PIECEWISE(w, O, new Float64Array(scale).buffer); + } else switch (scale) { + case "linear": + module = TRAFO_LINEAR(w, O); + break; + case "decibel": + O.trafo_reverse = 1; + module = TRAFO_LOG(w, O); + break; + case "log2": + O.trafo_reverse = 0; + module = TRAFO_LOG(w, O); + break; + case "frequency": + O.trafo_reverse = 0; + module = TRAFO_FREQ(w, O); + break; + case "frequency-reverse": + O.trafo_reverse = 1; + module = TRAFO_FREQ(w, O); + break; + default: + TK.warn("Unsupported scale", scale); + } + + Object.assign(this, module); +} +function set_cb(key, value) { + switch (key) { + case "min": + case "max": + case "snap": + update_snap.call(this); + /* fall through */ + case "log_factor": + case "scale": + case "reverse": + case "basis": + update_transformation.call(this); + this.fire_event("rangedchanged"); + break; + } +} +/** + * @callback TK.Ranged~scale_cb + * + * @param {number} value - The value to be transformed. + * @param {Object} [options={ }] - An object containing initial options. - The options of the corresponding {@link TK.Ranged} object. + * @param {boolean} [inverse=false] - Determines if the value is to be transformed from or + * to the coordinate system. + * + * @returns {number} The transformed value. + */ +TK.Ranged = TK.class({ + /** + * TK.Ranged combines functionality for two distinct purposes. + * Firstly, TK.Ranged can be used to snap values to a virtual grid. + * This grid is defined by the options snap, + * step, min, max and base. + * The second feature of TK.anged is that it allows transforming values between coordinate systems. + * This can be used to transform values from and to linear scales in which they are displayed on the + * screen. It is used inside of Toolkit to translate values (e.g. in Hz or dB) to pixel positions or + * percentages, for instance in widgets such as {@link TK.Scale}, {@link TK.MeterBase} or + * {@link TK.Graph}. + * + * TK.Ranged features several types of coordinate systems which are often used in audio applications. + * They can be configured using the options.scale option, possible values are: + *
    + *
  • linear for linear coordinates, + *
  • decibel for linear coordinates, + *
  • log2 for linear coordinates, + *
  • frequency for linear coordinates or + *
  • frequency-reverse" for linear coordinates. + *
+ * If options.scale is a function, it is used as the coordinate transformation. + * Its signature is {@link TK.Ranged~scale_cb}. This allows the definition of custom + * coordinate transformations, which go beyond the standard types. + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {String|Array|Function} [options.scale="linear"] - + * The type of the scale. Either one of linear, decibel, log2, + * frequency or frequency-reverse; or an array containing a + * piece-wise linear scale; + * or a callback function of type {@link TK.Ranged~scale_cb}. + * @property {Boolean} [options.reverse=false] - Reverse the scale of the range. + * @property {Number} [options.basis=1] - The size of the linear scale. Set to pixel width or height + * if used for drawing purposes or to 100 for percentages. + * @property {Number} [options.min=0] - Minimum value of the range. + * @property {Number} [options.max=1] - Maximum value of the range. + * @property {Number} [options.log_factor=1] - Used to overexpand logarithmic curves. 1 keeps the + * natural curve while values above 1 will overbend. + * @property {Number|Array.} [options.snap=0] - + * Defines a virtual grid. + * If options.snap is a positive number, it is interpreted as the distance of + * grid points. + * Then, inside of the interval options.min ... options.max the grid + * points are options.base + n * options.snap where n is any + * integer. Any values outside of that interval are snapped to the biggest or smallest grid + * point, respectively. + * In order to define grids with non-uniform spacing, set options.snap to an Array + * of grid points. + * @property {Number} [options.base=0] - Base point. Used e.g. to mark 0dB on a fader from -96dB to 12dB. + * @property {Number} [options.step=0] - Step size. Used for instance by {@link TK.ScrollValue} + * as the step size. + * @property {Number} [options.shift_up=4] - Multiplier for increased stepping speed, e.g. used by + * {@link TK.ScrollValue} when simultaneously pressing 'shift'. + * @property {Number} [options.shift_down=0.25] - Multiplier for descresed stepping speed, e.g. used by + * {@link TK.ScrollValue} when simultaneously pressing 'shift' and 'ctrl'. + * + * @mixin TK.Ranged + */ + + _class: "Ranged", + options: { + scale: "linear", + reverse: false, + basis: 1, + min: 0, + max: 1, + base: 0, + step: 0, + shift_up: 4, + shift_down: 0.25, + snap: 0, + round: true, /* default for TK.Range, no dedicated option */ + log_factor: 1, + trafo_reverse: false, /* used internally, no documentation */ + }, + _options: { + scale: "string|array|function", + reverse: "boolean", + basis: "number", + min: "number", + max: "number", + base: "number", + step: "number", + shift_up: "number", + shift_down: "number", + snap: "mixed", + round: "boolean", + log_factor: "number", + trafo_reverse: "boolean", + }, + static_events: { + set: set_cb, + initialized: function() { + var O = this.options; + if (!(O.min <= O.max)) + TK.warn("Ranged needs min <= max. min: ", O.min, ", max:", O.max, ", options:", O); + update_snap.call(this); + update_transformation.call(this); + }, + }, +}); +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/implements/ranges.js b/share/web_surfaces/builtin/mixer/toolkit/implements/ranges.js new file mode 100644 index 0000000000..1f1738c0fc --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/implements/ranges.js @@ -0,0 +1,83 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function(w, TK){ +/** + * Ranges provides multiple {@link TK.Range}s for a widget. They + * can be used for building coordinate systems. + * + * @mixin TK.Ranges + */ +function range_changed(value, name) { + var range = this[name]; + for (var i in value) { + range.set(i, value[i]); + } +} +TK.Ranges = TK.class({ + _class: "Ranges", + /** + * Add a new {@link TK.Range}. If name is set and this.options[name] + * exists, is an object and from is an object, too, both are merged + * before a range is created. + * + * @method TK.Ranges#add_range + * + * @param {Function|Object} from - A function returning a {@link TK.Range} + * instance or an object containing options for a new {@link TK.Range}. + * @param {string} name - Designator of the {@link TK.Range}. + * If a name is set a new set function is added to the item to + * set the options of the {@link TK.Range}. Use the set function like this: + * this.set("name", {key: value}); + * + * @emits TK.Ranges#rangeadded + * + * @returns {TK.Range} The new {@link TK.Range}. + */ + add_range: function (from, name) { + var r; + if (typeof from === "function") { + r = from(); + } else if (TK.Ranged.prototype.isPrototypeOf(from)) { + r = TK.Range(from.options); + } else if (TK.Range.prototype.isPrototypeOf(from)) { + r = from; + } else { + if (name + && this.options[name] + && typeof this.options[name] === "object") + from = Object.assign({}, this.options[name], from) + r = new TK.Range(from); + } + if (name) { + this[name] = r; + this.add_event("set_"+name, range_changed); + } + /** + * Gets fired when a new range is added + * + * @event TK.Ranges#rangeadded + * + * @param {TK.Range} range - The {@link TK.Range} that was added. + */ + this.fire_event("rangeadded", r); + return r; + } +}) +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/implements/warning.js b/share/web_surfaces/builtin/mixer/toolkit/implements/warning.js new file mode 100644 index 0000000000..bd8c0a8dd7 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/implements/warning.js @@ -0,0 +1,58 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function(w, TK){ +/** + * Adds the class "toolkit-warn" on this.element for a certain + * period of time. It is used e.g. in {@link TK.ResponseHandle} or {@link TK.Knob} when the value + * exceeds the range. + * + * @mixin TK.Warning + */ +TK.Warning = TK.class({ + _class: "Warning", + /** + * Adds the class toolkit-warn to the given element and + * sets a timeout after which the class is removed again. If there + * already is a timeout waiting it gets updated. + * + * @method TK.Warning#warning + * + * @emits TK.Warning#warning + * + * @param {HTMLElement|SVGElement} element - The DOM node the class should be added to. + * @param {Number} [timeout=250] - The timeout in ms until the class should be removed again. + */ + warning: function (element, timeout) { + if (!timeout) timeout = 250; + if (this.__wto) window.clearTimeout(this.__wto); + this.__wto = null; + TK.add_class(element, "toolkit-warn"); + this.__wto = window.setTimeout(function () { + TK.remove_class(element, "toolkit-warn"); + }.bind(this), timeout); + /** + * Gets fired when {@link TK.Warning#warning} was called. + * + * @event TK.Warning#warning + */ + this.fire_event("warning"); + } +}); +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/modules/README b/share/web_surfaces/builtin/mixer/toolkit/modules/README new file mode 100644 index 0000000000..33a9116298 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/modules/README @@ -0,0 +1,6 @@ +@category: Modules + +Modules are Classes to build other widgets from. They normally can't +exist on their own and require a parent item which makes use of them. +For example: Graph is mainly a SVG path which needs a parent item containing +a SVG image. diff --git a/share/web_surfaces/builtin/mixer/toolkit/modules/circular.js b/share/web_surfaces/builtin/mixer/toolkit/modules/circular.js new file mode 100644 index 0000000000..2640f88f11 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/modules/circular.js @@ -0,0 +1,626 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function (w, TK) { +function interpret_label(x) { + if (typeof x === "object") return x; + if (typeof x === "number") return { pos: x }; + TK.error("Unsupported label type ", x); +} +var __rad = Math.PI / 180; +function _get_coords(deg, inner, outer, pos) { + deg = +deg; + inner = +inner; + outer = +outer; + pos = +pos; + deg = deg * __rad; + return { + x1: Math.cos(deg) * outer + pos, + y1: Math.sin(deg) * outer + pos, + x2: Math.cos(deg) * inner + pos, + y2: Math.sin(deg) * inner + pos + } +} +function _get_coords_single(deg, inner, pos) { + deg = +deg; + inner = +inner; + pos = +pos; + deg = deg * __rad; + return { + x: Math.cos(deg) * inner + pos, + y: Math.sin(deg) * inner + pos + } +} +var format_path = TK.FORMAT("M %f,%f " + + "A %f,%f 0 %d,%d %f,%f " + + "L %f,%f " + + "A %f,%f 0 %d,%d %f,%f z"); +var format_translate = TK.FORMAT("translate(%f, %f)"); +var format_translate_rotate = TK.FORMAT("translate(%f %f) rotate(%f %f %f)"); +var format_rotate = TK.FORMAT("rotate(%f %f %f)"); + +function draw_dots() { + // depends on dots, dot, min, max, size + var _dots = this._dots; + var O = this.options; + var dots = O.dots; + var dot = O.dot; + var angle = O.angle; + TK.empty(_dots); + for (var i = 0; i < dots.length; i++) { + var m = dots[i]; + var r = TK.make_svg("rect", {"class": "toolkit-dot"}); + + var length = m.length === void(0) + ? dot.length : m.length; + var width = m.width === void(0) + ? dot.width : m.width; + var margin = m.margin === void(0) + ? dot.margin : m.margin; + var pos = Math.min(O.max, Math.max(O.min, m.pos)); + // TODO: consider adding them all at once + _dots.appendChild(r); + if (m["class"]) TK.add_class(r, m["class"]); + if (m.color) r.style.fill = m.color; + + r.setAttribute("x", O.size - length - margin); + r.setAttribute("y", O.size / 2 - width / 2); + + r.setAttribute("width", length); + r.setAttribute("height", width); + + r.setAttribute("transform", "rotate(" + + (this.val2coef(this.snap(pos)) * angle) + " " + + (O.size / 2) + " " + (this.options.size / 2) + ")"); + } + /** + * Is fired when dots are (re)drawn. + * @event TK.Circular#dotsdrawn + */ + this.fire_event("dotsdrawn"); +} +function draw_markers() { + // depends on size, markers, marker, min, max + var I = this.invalid; + var O = this.options; + var markers = O.markers; + var marker = O.marker; + TK.empty(this._markers); + + var stroke = this._get_stroke(); + var outer = O.size / 2; + var angle = O.angle; + + for (var i = 0; i < markers.length; i++) { + var m = markers[i]; + var thick = m.thickness === void(0) + ? marker.thickness : m.thickness; + var margin = m.margin === void(0) + ? marker.margin : m.margin; + var inner = outer - thick; + var outer_p = outer - margin - stroke / 2; + var inner_p = inner - margin - stroke / 2; + var from, to; + + if (m.from === void(0)) + from = O.min; + else + from = Math.min(O.max, Math.max(O.min, m.from)); + + if (m.to === void(0)) + to = O.max; + else + to = Math.min(O.max, Math.max(O.min, m.to)); + + var s = TK.make_svg("path", {"class": "toolkit-marker"}); + this._markers.appendChild(s); + + if (m["class"]) TK.add_class(s, m["class"]); + if (m.color) s.style.fill = m.color; + if (!m.nosnap) { + from = this.snap(from); + to = this.snap(to); + } + from = this.val2coef(from) * angle; + to = this.val2coef(to) * angle; + + draw_slice.call(this, from, to, inner_p, outer_p, outer, s); + } + /** + * Is fired when markers are (re)drawn. + * @event TK.Circular#markersdrawn + */ + this.fire_event("markersdrawn"); +} +function draw_labels() { + // depends on size, labels, label, min, max, start + var _labels = this._labels; + var O = this.options; + var labels = O.labels; + TK.empty(this._labels); + + if (!labels.length) return; + + var outer = O.size / 2; + var a = new Array(labels.length); + var i; + + var l, p, positions = new Array(labels.length); + + for (i = 0; i < labels.length; i++) { + l = labels[i]; + p = TK.make_svg("text", {"class": "toolkit-label", + style: "dominant-baseline: central;" + }); + + if (l["class"]) TK.add_class(p, l["class"]); + if (l.color) p.style.fill = l.color; + + + if (l.label !== void(0)) + p.textContent = l.label; + else + p.textContent = O.label.format(l.pos); + + p.setAttribute("text-anchor", "middle"); + + _labels.appendChild(p); + a[i] = p; + } + /* FORCE_RELAYOUT */ + + TK.S.add(function() { + var i, p; + for (i = 0; i < labels.length; i++) { + l = labels[i]; + p = a[i]; + + var margin = l.margin !== void(0) ? l.margin : O.label.margin; + var align = (l.align !== void(0) ? l.align : O.label.align) === "inner"; + var pos = Math.min(O.max, Math.max(O.min, l.pos)); + var bb = p.getBBox(); + var angle = (this.val2coef(this.snap(pos)) * O.angle + O.start) % 360; + var outer_p = outer - margin; + var coords = _get_coords_single(angle, outer_p, outer); + + var mx = ((coords.x - outer) / outer_p) * (bb.width + bb.height / 2.5) / (align ? -2 : 2); + var my = ((coords.y - outer) / outer_p) * bb.height / (align ? -2 : 2); + + positions[i] = format_translate(coords.x + mx, coords.y + my); + } + + TK.S.add(function() { + for (i = 0; i < labels.length; i++) { + p = a[i]; + p.setAttribute("transform", positions[i]); + } + /** + * Is fired when labels are (re)drawn. + * @event TK.Circular#labelsdrawn + */ + this.fire_event("labelsdrawn"); + }.bind(this), 1); + }.bind(this)); +} +function draw_slice(a_from, a_to, r_inner, r_outer, pos, slice) { + a_from = +a_from; + a_to = +a_to; + r_inner = +r_inner; + r_outer = +r_outer; + pos = +pos; + // ensure from !== to + if(a_from % 360 === a_to % 360) a_from += 0.001; + // ensure from and to in bounds + while (a_from < 0) a_from += 360; + while (a_to < 0) a_to += 360; + if (a_from > 360) a_from %= 360; + if (a_to > 360) a_to %= 360; + // get drawing direction (sweep = clock-wise) + if (this.options.reverse && a_to <= a_from + || !this.options.reverse && a_to > a_from) + var sweep = 1; + else + var sweep = 0; + // get large flag + if (Math.abs(a_from - a_to) >= 180) + var large = 1; + else + var large = 0; + // draw this slice + var from = _get_coords(a_from, r_inner, r_outer, pos); + var to = _get_coords(a_to, r_inner, r_outer, pos); + + var path = format_path(from.x1, from.y1, + r_outer, r_outer, large, sweep, to.x1, to.y1, + to.x2, to.y2, + r_inner, r_inner, large, !sweep, from.x2, from.y2); + slice.setAttribute("d", path); +} +TK.Circular = TK.class({ + /** + * TK.Circular is a SVG group element containing two paths for displaying + * numerical values in a circular manner. TK.Circular is able to draw labels, + * dots and markers and can show a hand. TK.Circular e.g. is implemented by + * {@link TK.Clock} to draw hours, minutes and seconds. + * + * @class TK.Circular + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {Number} [options.value=0] - Sets the value on the hand and on the + * ring at the same time. + * @property {Number} [options.value_hand=0] - Sets the value on the hand. + * @property {Number} [options.value_ring=0] - Sets the value on the ring. + * @property {Number} [options.size=100] - The diameter of the circle. This + * is the base value for all following layout-related parameters. Keeping + * it set to 100 offers percentual lenghts. Set the final size of the widget + * via CSS. + * @property {Number} [options.thickness=3] - The thickness of the circle. + * @property {Number} [options.margin=0] - The margin between base and value circles. + * @property {Boolean} [options.show_hand=true] - Draw the hand. + * @property {Object} [options.hand] - Dimensions of the hand. + * @property {Number} [options.hand.width=2] - Width of the hand. + * @property {Number} [options.hand.length=30] - Length of the hand. + * @property {Number} [options.hand.margin=10] - Margin of the hand. + * @property {Number} [options.start=135] - The starting point in degrees. + * @property {Number} [options.angle=270] - The maximum degree of the rotation when + * options.value === options.max. + * @property {Number|Boolean} [options.base=false] - If a base value is set in degrees, + * circular starts drawing elements from this position. + * @property {Boolean} [options.show_base=true] - Draw the base ring. + * @property {Boolean} [options.show_value=true] - Draw the value ring. + * @property {Number} [options.x=0] - Horizontal displacement of the circle. + * @property {Number} [options.y=0] - Vertical displacement of the circle. + * @property {Boolean} [options.show_dots=true] - Show/hide all dots. + * @property {Object} [options.dot] - This option acts as default values for the individual dots + * specified in options.dots. + * @property {Number} [options.dot.width=2] - Width of the dots. + * @property {Number} [options.dot.length=2] - Length of the dots. + * @property {Number} [options.dot.margin=5] - Margin of the dots. + * @property {Array} [options.dots=[]] - An array of objects describing where dots should be placed + * along the circle. Members are position pos in the value range and optionally + * color and class and any of the properties of options.dot. + * @property {Boolean} [options.show_markers=true] - Show/hide all markers. + * @property {Object} [options.marker] - This option acts as default values of the individual markers + * specified in options.markers. + * @property {Number} [options.marker.thickness=3] - Thickness of the marker. + * @property {Number} [options.marker.margin=3] - Margin of the marker. + * @property {Array} [options.markers=[]] - An array containing objects which describe where markers + * are to be places. Members are the position as from and to and optionally + * color, class and any of the properties of options.marker. + * @property {Boolean} [options.show_labels=true] - Show/hide all labels. + * @property {Object} [options.label] - This option acts as default values for the individual labels + * specified in options.labels. + * @property {Integer} [options.label.margin=8] - Distance of the label from the circle of diameter + * options.size. + * @property {String} [options.label.align="outer"] - This option controls if labels are positioned + * inside or outside of the circle with radius options.size/2 - margin. + * @property {Function} [options.label.format] - Optional formatting function for the label. + * Receives the label value as first argument. + * @property {Array} [options.labels=[]] - An array containing objects which describe labels + * to be displayed. Either a value or an object whose members are the position pos + * insie the value range and optionally color, class and any of the + * properties of options.label. + * + * @extends TK.Widget + * + * @mixes TK.Warning + * + * @mixes TK.Ranged + */ + _class: "Circular", + Extends: TK.Widget, + Implements: [TK.Warning, TK.Ranged], + _options: Object.assign(Object.create(TK.Widget.prototype._options), TK.Ranged.prototype._options, { + value: "number", + value_hand: "number", + value_ring: "number", + size: "number", + thickness: "number", + margin: "number", + hand: "object", + start: "number", + angle: "number", + base: "number|boolean", + show_base: "boolean", + show_value: "boolean", + show_hand: "boolean", + x: "number", + y: "number", + dot: "object", + dots: "array", + marker: "object", + markers: "array", + label: "object", + labels: "array" + }), + static_events: { + set_value: function(value) { + this.set("value_hand", value); + this.set("value_ring", value); + }, + initialized: function() { + // calculate the stroke here once. this happens before + // the initial redraw + TK.S.after_frame(this._get_stroke.bind(this)); + this.set("value", this.options.value); + }, + }, + options: { + value: 0, + value_hand: 0, + value_ring: 0, + size: 100, + thickness: 3, + margin: 0, + hand: {width: 2, length: 30, margin: 10}, + start: 135, + angle: 270, + base: false, + show_base: true, + show_value: true, + show_hand: true, + x: 0, + y: 0, + dot: {width: 2, length: 2, margin: 5}, + dots: [], + marker: {thickness: 3, margin: 0}, + markers: [], + label: {margin: 8, align: "inner", format: function(val){return val;}}, + labels: [] + }, + + initialize: function (options) { + TK.Widget.prototype.initialize.call(this, options); + var E; + + /** + * @member {SVGImage} TK.Circular#element - The main SVG element. + * Has class toolkit-circular + */ + this.element = E = TK.make_svg("g", {"class": "toolkit-circular"}); + this.widgetize(E, true, true, true); + + /** + * @member {SVGPath} TK.Circular#_base - The base of the ring. + * Has class toolkit-base + */ + this._base = TK.make_svg("path", {"class": "toolkit-base"}); + E.appendChild(this._base); + + /** + * @member {SVGPath} TK.Circular#_value - The ring showing the value. + * Has class toolkit-value + */ + this._value = TK.make_svg("path", {"class": "toolkit-value"}); + E.appendChild(this._value); + + /** + * @member {SVGRect} TK.Circular#_hand - The hand of the knob. + * Has class toolkit-hand + */ + this._hand = TK.make_svg("rect", {"class": "toolkit-hand"}); + E.appendChild(this._hand); + + if (this.options.labels) + this.set("labels", this.options.labels); + }, + + resize: function () { + this.invalid.labels = true; + this.trigger_draw(); + TK.Widget.prototype.resize.call(this); + }, + + redraw: function () { + TK.Widget.prototype.redraw.call(this); + var I = this.invalid; + var O = this.options; + var E = this.element; + var outer = O.size / 2; + var tmp; + + if (I.validate("x", "y") || I.start || I.size) { + E.setAttribute("transform", format_translate_rotate(O.x, O.y, O.start, outer, outer)); + this._labels.setAttribute("transform", format_rotate(-O.start, outer, outer)); + } + + if (O.show_labels && (I.validate("show_labels", "labels", "label") || + I.size || I.min || I.max || I.start)) { + draw_labels.call(this); + } + + if (O.show_dots && (I.validate("show_dots", "dots", "dot") || + I.min || I.max || I.size)) { + draw_dots.call(this); + } + + if (O.show_markers && (I.validate("show_markers", "markers", "marker") || + I.size || I.min || I.max)) { + draw_markers.call(this); + } + + var stroke = this._get_stroke(); + var inner = outer - O.thickness; + var outer_p = outer - stroke / 2 - O.margin; + var inner_p = inner - stroke / 2 - O.margin; + + if (I.show_value || I.value_ring) { + I.show_value = false; + if (O.show_value) { + draw_slice.call(this, this.val2coef(this.snap(O.base)) * O.angle, this.val2coef(this.snap(O.value_ring)) * O.angle, inner_p, outer_p, outer, + this._value); + } else { + this._value.removeAttribute("d"); + } + } + + if (I.show_base) { + I.show_base = false; + if (O.show_base) { + draw_slice.call(this, 0, O.angle, inner_p, outer_p, outer, this._base); + } else { + /* TODO: make this a child element */ + this._base.removeAttribute("d"); + } + } + if (I.show_hand) { + I.show_hand = false; + if (O.show_hand) { + this._hand.style.display = "block"; + } else { + this._hand.style.display = "none"; + } + } + if (I.validate("size", "value_hand", "hand", "min", "max", "start")) { + tmp = this._hand; + tmp.setAttribute("x", O.size - O.hand.length - O.hand.margin); + tmp.setAttribute("y", (O.size - O.hand.width) / 2.0); + tmp.setAttribute("width", O.hand.length); + tmp.setAttribute("height",O.hand.width); + tmp.setAttribute("transform", + format_rotate(this.val2coef(this.snap(O.value_hand)) * O.angle, O.size / 2, O.size / 2)); + } + }, + + destroy: function () { + this._dots.remove(); + this._markers.remove(); + this._base.remove(); + this._value.remove(); + TK.Widget.prototype.destroy.call(this); + }, + _get_stroke: function () { + if (this.hasOwnProperty("_stroke")) return this._stroke; + var strokeb = parseInt(TK.get_style(this._base, "stroke-width")) || 0; + var strokev = parseInt(TK.get_style(this._value, "stroke-width")) || 0; + this._stroke = Math.max(strokeb, strokev); + return this._stroke; + }, + + /** + * Adds a label. + * + * @method TK.Circular#add_label + * @param {Object|Number} label - The label. Please refer to the initial options + * to learn more about possible values. + * @returns {Object} label - The interpreted object to build the label from. + */ + add_label: function(label) { + var O = this.options; + + if (!O.labels) { + O.labels = []; + } + + label = interpret_label(label); + + if (label) { + O.labels.push(label); + this.invalid.labels = true; + this.trigger_draw(); + return label; + } + }, + + /** + * Removes a label. + * + * @method TK.Circular#remove_label + * @param {Object} label - The label object as returned from `add_label`. + * @returns {Object} label - The removed label options. + */ + remove_label: function(label) { + var O = this.options; + + if (!O.labels) return; + + var i = O.labels.indexOf(label); + + if (i === -1) return; + + O.labels.splice(i); + this.invalid.labels = true; + this.trigger_draw(); + }, + + // GETTERS & SETTERS + set: function (key, value) { + switch (key) { + case "dot": + case "marker": + case "label": + value = Object.assign(this.options[key], value); + break; + case "base": + if (value === false) value = this.options.min; + break; + case "value": + if (value > this.options.max || value < this.options.min) + this.warning(this.element); + value = this.snap(value); + break; + case "labels": + if (value) + for (var i = 0; i < value.length; i++) { + value[i] = interpret_label(value[i]); + } + break; + } + + return TK.Widget.prototype.set.call(this, key, value); + } +}); +/** + * @member {SVGGroup} TK.Circular#_markers - A SVG group containing all markers. + * Has class toolkit-markers + */ +TK.ChildElement(TK.Circular, "markers", { + //option: "markers", + //display_check: function(v) { return !!v.length; }, + show: true, + create: function() { + return TK.make_svg("g", {"class": "toolkit-markers"}); + }, +}); +/** + * @member {SVGGroup} TK.Circular#_dots - A SVG group containing all dots. + * Has class toolkit-dots + */ +TK.ChildElement(TK.Circular, "dots", { + //option: "dots", + //display_check: function(v) { return !!v.length; }, + show: true, + create: function() { + return TK.make_svg("g", {"class": "toolkit-dots"}); + }, +}); +/** + * @member {SVGGroup} TK.Circular#_labels - A SVG group containing all labels. + * Has class toolkit-labels + */ +TK.ChildElement(TK.Circular, "labels", { + //option: "labels", + //display_check: function(v) { return !!v.length; }, + show: true, + create: function() { + return TK.make_svg("g", {"class": "toolkit-labels"}); + }, +}); +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/modules/drag.js b/share/web_surfaces/builtin/mixer/toolkit/modules/drag.js new file mode 100644 index 0000000000..d1c3fa8a54 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/modules/drag.js @@ -0,0 +1,179 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function(w, TK){ + +function extract_matrix (t) { + var a = t.indexOf("matrix("); + if (a < 0) return; + t = t.substr(a+7); + return (t.split(")"))[0].split(",").map(function(v){return parseInt(v.trim())}); +} + +function xy_from_transform (t) { + var mx = extract_matrix(t); + return (!mx || !mx.length) ? [0, 0] : [mx[4], mx[5]]; +} + +function startdrag(e, drag) { + this._dragged = 0; + var O = this.options; + if (!O.active) return; + if (e.button !== void(0) && e.button > 0) return; + this._xstart = this._xlast = e.pageX; + this._ystart = this._ylast = e.pageY; + if (O.transform) { + var xy = xy_from_transform(this._style.transform); + this._xpos = xy[0]; + this._ypos = xy[1]; + } else { + this._xpos = O.node.offsetLeft; + this._ypos = O.node.offsetTop; + } + TK.add_class(O.node, "toolkit-dragging"); +} +function stopdrag(e, drag) { + if (!this.options.active) return; + if (e.button !== void(0) && e.button > 0) return; + TK.remove_class(this.options.node, "toolkit-dragging"); +} +function dragging(e, drag) { + var O = this.options; + if (!O.active) return; + if (e.button !== void(0) && e.button > 0) return; + this._dragged += (Math.abs(e.pageX - this._xlast) + + Math.abs(e.pageY - this._ylast)) / 2; + if (this._dragged < O.initial) return; + this._xlast = e.pageX; + this._ylast = e.pageY; + var x = this._xpos + e.pageX - this._xstart; + var y = this._ypos + e.pageY - this._ystart; + if (O.min.x !== false) x = Math.max(O.min.x, x); + if (O.max.x !== false) x = Math.min(O.max.x, x); + if (O.min.y !== false) y = Math.max(O.min.y, y); + if (O.max.y !== false) y = Math.min(O.max.y, y); + if (O.transform) { + var t = this._style.transform; + var mx = extract_matrix(t); + mx[4] = x; + mx[5] = y; + var nt = t.replace(/matrix\([0-9 \,]*\)/, "matrix(" + mx.join(",") + ")"); + O.node.style.transform = nt; + } else { + O.node.style.top = y + "px"; + O.node.style.left = x + "px"; + } +} +function set_handle() { + var h = this.options.handle; + if (this.drag) + this.drag.destroy(); + var range = new TK.Range({}); + this.drag = new TK.DragValue(this, { + node: h, + range: function () { return range; }, + get: function() { return 0; }, + set: function(v) { return; }, + }); +} +/** + * TK.Drag enables dragging of elements on the screen positioned absolutely or by CSS transformation. + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {HTMLElement|SVGElement} options.node - The element to drag. + * @property {HTMLElement|SVGElement} [options.handle] A DOM node to be used as a handle. If not set, options.node is used. + * @property {Boolean} [options.active=true] - Enable or disable dragging + * @property {Object|Boolean} [options.min={x: false, y: false}] - Object containing the minimum positions for x and y. A value of false is interpreted as no minimum. + * @property {Object|Boolean} [options.max={x: false, y: false}] - Object containing the maximum positions for x and y. A value of false is interpreted as no maximum. + * @property {Number} [options.initial=2] - Number of pixels the user has to move until dragging starts. + * @property {Boolean} [options.transform=false] - Use CSS transformations instead of absolute positioning. + * + * @extends TK.Base + * + * @class TK.Drag + */ +TK.Drag = TK.class({ + _class: "Drag", + Extends: TK.Base, + _options: { + node : "object", + handle : "object", + active : "boolean", + min : "object", + max : "object", + initial : "number", + transform : "boolean", + }, + options: { + node : null, + handle : null, + active : true, + min : {x: false, y: false}, + max : {x: false, y: false}, + initial : 2, + transform : false, + }, + /** + * The user is dragging this item. + * + * @event TK.Drag#dragging + * + * @param {DOMEvent} event - The native DOM event. + */ + /** + * The user started dragging this item. + * + * @event TK.Drag#startdrag + * + * @param {DOMEvent} event - The native DOM event. + */ + /** + * The user stopped dragging this item. + * + * @event TK.Drag#stopdrag + * + * @param {DOMEvent} event - The native DOM event. + */ + static_events: { + startdrag: startdrag, + dragging: dragging, + stopdrag: stopdrag, + }, + initialize: function (options) { + TK.Base.prototype.initialize.call(this, options); + this.set("handle", this.options.handle); + this.set("node", this.options.node); + }, + // GETTERS & SETTERS + set: function (key, value) { + if (key === "node") + this._style = window.getComputedStyle(value); + if (key === "handle" && !value) + value = this.options.node; + + TK.Base.prototype.set.call(this, key, value); + + if (key === "handle") + set_handle.call(this); + if (key === "initial" && this.drag) + this.drag.set("initial", value); + } +}); +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/modules/dragcapture.js b/share/web_surfaces/builtin/mixer/toolkit/modules/dragcapture.js new file mode 100644 index 0000000000..76376802b7 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/modules/dragcapture.js @@ -0,0 +1,345 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function(w, TK){ +var document = window.document; + +/* this has no global symbol */ +function CaptureState(start) { + this.start = start; + this.prev = start; + this.current = start; +} +CaptureState.prototype = { + /* distance from start */ + distance: function() { + var v = this.vdistance(); + return Math.sqrt(v[0]*v[0] + v[1]*v[1]); + }, + set_current: function(ev) { + this.prev = this.current; + this.current = ev; + return true; + }, + vdistance: function() { + var start = this.start; + var current = this.current; + return [ current.clientX - start.clientX, current.clientY - start.clientY ]; + }, + prev_distance: function() { + var prev = this.prev; + var current = this.current; + return [ current.clientX - prev.clientX, current.clientY - prev.clientY ]; + }, +}; +/* general api */ +function startcapture(state) { + /* do nothing, let other handlers be called */ + if (this.drag_state) return; + + /** + * Capturing started. + * + * @event TK.DragCapture#startcapture + * + * @param {object} state - An internal state object. + * @param {DOMEvent} start - The event object of the initial event. + */ + + var v = this.fire_event("startcapture", state, state.start); + + if (v === true) { + /* we capture this event */ + this.drag_state = state; + this.set("state", true); + } + + return v; +} +function movecapture(ev) { + var d = this.drag_state; + + /** + * A movement was captured. + * + * @event TK.DragCapture#movecapture + * + * @param {DOMEvent} event - The event object of the current move event. + */ + + if (!d.set_current(ev) || this.fire_event("movecapture", d) === false) { + stopcapture.call(this, ev); + return false; + } +} +function stopcapture(ev) { + var s = this.drag_state; + if (s === null) return; + + /** + * Capturing stopped. + * + * @event TK.DragCapture#stopcapture + * + * @param {object} state - An internal state object. + * @param {DOMEvent} event - The event object of the current event. + */ + + this.fire_event("stopcapture", s, ev); + this.set("state", false); + s.destroy(); + this.drag_state = null; +} + +/* mouse handling */ +function MouseCaptureState(start) { + this.__mouseup = null; + this.__mousemove = null; + CaptureState.call(this, start); +} +MouseCaptureState.prototype = Object.assign(Object.create(CaptureState.prototype), { + set_current: function(ev) { + var start = this.start; + /* If the buttons have changed, we assume that the capture has ended */ + if (!this.is_dragged_by(ev)) return false; + return CaptureState.prototype.set_current.call(this, ev); + }, + init: function(widget) { + this.__mouseup = mouseup.bind(widget); + this.__mousemove = mousemove.bind(widget); + document.addEventListener("mousemove", this.__mousemove); + document.addEventListener("mouseup", this.__mouseup); + }, + destroy: function() { + document.removeEventListener("mousemove", this.__mousemove); + document.removeEventListener("mouseup", this.__mouseup); + this.__mouseup = null; + this.__mousemove = null; + }, + is_dragged_by: function(ev) { + var start = this.start; + if (start.buttons !== ev.buttons || start.which !== ev.which) return false; + return true; + }, +}); +function mousedown(ev) { + var s = new MouseCaptureState(ev); + var v = startcapture.call(this, s); + + /* ignore this event */ + if (v === void(0)) return; + + ev.stopPropagation(); + ev.preventDefault(); + + /* we did capture */ + if (v === true) s.init(this); + + return false; +} +function mousemove(ev) { + movecapture.call(this, ev); +} +function mouseup(ev) { + stopcapture.call(this, ev); +} + +/* touch handling */ + +/* + * Old Safari versions will keep the same Touch objects for the full lifetime + * and simply update the coordinates, etc. This is a bug, which we work around by + * cloning the information we need. + */ +function clone_touch(t) { + return { + clientX: t.clientX, + clientY: t.clientY, + identifier: t.identifier, + }; +} + +function TouchCaptureState(start) { + CaptureState.call(this, start); + var touch = start.changedTouches.item(0); + touch = clone_touch(touch); + this.stouch = touch; + this.ptouch = touch; + this.ctouch = touch; +} +TouchCaptureState.prototype = Object.assign(Object.create(CaptureState.prototype), { + find_touch: function(ev) { + var id = this.stouch.identifier; + var touches = ev.changedTouches; + var touch; + + for (var i = 0; i < touches.length; i++) { + touch = touches.item(i); + if (touch.identifier === id) return touch; + } + + return null; + }, + set_current: function(ev) { + var touch = clone_touch(this.find_touch(ev)); + this.ptouch = this.ctouch; + this.ctouch = touch; + return CaptureState.prototype.set_current.call(this, ev); + }, + vdistance: function() { + var start = this.stouch; + var current = this.ctouch; + return [ current.clientX - start.clientX, current.clientY - start.clientY ]; + }, + prev_distance: function() { + var prev = this.ptouch; + var current = this.ctouch; + return [ current.clientX - prev.clientX, current.clientY - prev.clientY ]; + }, + destroy: function() { + }, + is_dragged_by: function(ev) { + return this.find_touch(ev) !== null; + }, +}); +function touchstart(ev) { + /* if cancelable is false, this is an async touchstart, which happens + * during scrolling */ + if (!ev.cancelable) return; + + /* the startcapture event handler has return false. we do not handle this + * pointer */ + var v = startcapture.call(this, new TouchCaptureState(ev)); + + if (v === void(0)) return; + + ev.preventDefault(); + ev.stopPropagation(); + return false; +} +function touchmove(ev) { + if (!this.drag_state) return; + /* we are scrolling, ignore the event */ + if (!ev.cancelable) return; + /* if we cannot find the right touch, some other touchpoint + * triggered this event and we do not care about that */ + if (!this.drag_state.find_touch(ev)) return; + /* if movecapture returns false, the capture has ended */ + if (movecapture.call(this, ev) !== false) { + ev.preventDefault(); + ev.stopPropagation(); + return false; + } +} +function touchend(ev) { + var s; + if (!ev.cancelable) return; + s = this.drag_state; + /* either we are not dragging or it is another touch point */ + if (!s || !s.find_touch(ev)) return; + stopcapture.call(this, ev); + ev.stopPropagation(); + ev.preventDefault(); + return false; +} +function touchcancel(ev) { + return touchend.call(this, ev); +} +var dummy = function() {}; + +function get_parents(e) { + var ret = []; + if (Array.isArray(e)) e.map(function(e) { e = e.parentNode; if (e) ret.push(e); }); + else if (e = e.parentNode) ret.push(e); + return ret; +} + +var static_events = { + set_node: function(value) { + this.delegate_events(value); + }, + contextmenu: function() { return false; }, + delegated: [ + function(element, old_element) { + /* cancel the current capture */ + if (old_element) stopcapture.call(this); + }, + function(elem, old) { + /* NOTE: this works around a bug in chrome (#673102) */ + if (old) TK.remove_event_listener(get_parents(old), "touchstart", dummy); + if (elem) TK.add_event_listener(get_parents(elem), "touchstart", dummy); + } + ], + touchstart: touchstart, + touchmove: touchmove, + touchend: touchend, + touchcancel: touchcancel, + mousedown: mousedown, +}; + +TK.DragCapture = TK.class({ + + /** + * TK.DragCapture is a low-level class for tracking drag events on + * both, touch and mouse events. It can be used for implementing drag'n'drop + * functionality as well as dragging the value of e.g. {@link TK.Fader} or + * {@link TK.Knob}. {@link TK.DragValue} derives from TK.DragCapture. + * + * @extends TK.Module + * + * @param {Object} widget - The parent widget making use of DragValue. + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {HTMLElement} [options.node] - The DOM element receiving the drag events. If not set the widgets element is used. + * + * @class TK.DragCapture + */ + + Extends: TK.Module, + _class: "DragCapture", + _options: { + node: "object", + state: "boolean", /* internal, undocumented */ + }, + options: { + state: false, + }, + static_events: static_events, + initialize: function(widget, O) { + TK.Module.prototype.initialize.call(this, widget, O); + this.drag_state = null; + if (O.node === void(0)) O.node = widget.element; + this.set("node", O.node); + }, + destroy: function() { + TK.Base.prototype.destroy.call(this); + stopcapture.call(this); + }, + cancel_drag: stopcapture, + dragging: function() { + return this.options.state; + }, + state: function() { + return this.drag_state; + }, + is_dragged_by: function(ev) { + return this.drag_state !== null && this.drag_state.is_dragged_by(ev); + }, +}); +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/modules/dragvalue.js b/share/web_surfaces/builtin/mixer/toolkit/modules/dragvalue.js new file mode 100644 index 0000000000..6af4ebfbfd --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/modules/dragvalue.js @@ -0,0 +1,283 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function(w, TK){ + +function start_drag(value) { + if (!value) return; + var O = this.options; + this.start_pos = O.range.call(this).val2px(O.get.call(this)); + this.fire_event("startdrag", this.drag_state.start); + if (O.events) O.events.call(this).fire_event("startdrag", this.drag_state.start); +} + +/* This version integrates movements, instead + * of using the global change since the beginning */ +function movecapture_int(O, range, state) { + /* O.direction is always 'polar' here */ + + /* movement since last event */ + var v = state.prev_distance(); + var RO = range.options; + + if (!v[0] && !v[1]) return; + + var V = O._direction; + + var dist = Math.sqrt(v[0]*v[0] + v[1]*v[1]); + + var c = (V[0]*v[0] - V[1]*v[1]) / dist; + + if (Math.abs(c) > O._cutoff) return; + + if (v[0] * V[1] + v[1] * V[0] < 0) dist = -dist; + + var multi = RO.step || 1; + var e = state.current; + + if (e.ctrlKey || e.altKey) { + multi *= RO.shift_down; + } else if (e.shiftKey) { + multi *= RO.shift_up; + } + + dist *= multi; + var v = this.start_pos + dist; + + var nval = range.px2val(v); + if (O.limit) + O.set.call(this, Math.min(RO.max, Math.max(RO.min, nval))); + else + O.set.call(this, nval); + + if (!(nval > RO.min) || !(nval < RO.max)) return; + + this.start_pos = v; +} + +function movecapture_abs(O, range, state) { + var dist; + var RO = range.options + switch(O.direction) { + case "vertical": + dist = -state.vdistance()[1]; + break; + default: + TK.warn("Unsupported direction:", O.direction); + case "horizontal": + dist = state.vdistance()[0]; + break; + } + if (O.reverse) + dist *= -1; + + var multi = RO.step || 1; + var e = state.current; + + if (e.ctrlKey && e.shiftKey) { + multi *= RO.shift_down; + } else if (e.shiftKey) { + multi *= RO.shift_up; + } + + dist *= multi; + + var nval = range.px2val(this.start_pos + dist); + + if (O.limit) + O.set.call(this, Math.min(RO.max, Math.max(RO.min, nval))); + else + O.set.call(this, nval); +} + +function movecapture(state) { + var O = this.options; + + if (O.active === false) return false; + + var state = this.drag_state; + var range = O.range.call(this); + + if (O.direction === "polar") { + movecapture_int.call(this, O, range, state); + } else { + movecapture_abs.call(this, O, range, state); + } + + this.fire_event("dragging", state.current); + if (O.events) O.events.call(this).fire_event("dragging", state.current); +} + +function stop_drag(state, ev) { + this.fire_event("stopdrag", ev); + var O = this.options; + if (O.events) O.events.call(this).fire_event("stopdrag", ev); +} + +function angle_diff(a, b) { + // returns an unsigned difference between two angles + var d = (Math.abs(a - b) + 360) % 360; + return d > 180 ? 360 - d : d; +} +TK.DragValue = TK.class({ + /** + * TK.DragValue enables dragging an element and setting a + * value according to the dragged distance. TK.DragValue is for example + * used in {@link TK.Knob} and {@link TK.ValueButton}. + * + * @class TK.DragValue + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {Element} options.node - The DOM node used for dragging. + * All DOM events are registered with this Element. + * @property {Function} [options.range] - A function returning a + * {@link TK.Range} object for + * calculating the value. Returns its parent (usually having + * {@link TK.Ranged}-features) by default. + * @property {Function} [options.events] - Returns an element firing the + * events startdrag, dragging and stopdrag. + * By default it returns this.parent. + * @property {Element|boolean} [options.classes=false] - While dragging, the class + * toolkit-dragging will be added to this element. If set to false + * the class will be set on options.node. + * @property {Function} [options.get] - Callback function returning the value to drag. + * By default it returns this.parent.options.value. + * @property {Function} [options.set] - Callback function for setting the value. + * By default it calls this.parent.userset("value", [value]);. + * @property {String} [options.direction="polar"] - Direction for changing the value. + * Can be polar, vertical or horizontal. + * @property {Boolean} [options.active=true] - If false, dragging is deactivated. + * @property {Boolean} [options.cursor=false] - If true, a {@link TK.GlobalCursor} is set while dragging. + * @property {Number} [options.blind_angle=20] - If options.direction is polar, + * this is the angle of separation between positive and negative value changes + * @property {Number} [options.rotation=45] - Defines the angle of the center of the positive value + * changes. 0 means straight upward. For instance, a value of 45 leads to increasing value when + * moving towards top and right. + * @property {Boolean} [options.reverse=false] - If true, the difference of pointer travel is inverted. + * @property {Boolean} [options.limit=false] - Limit the returned value to min and max of the range. + * + * @extends TK.Module + * + * @mixes TK.GlobalCursor + */ + _class: "DragValue", + Extends: TK.DragCapture, + Implements: TK.GlobalCursor, + _options: { + get: "function", + set: "function", + range: "function", + events: "function", + classes: "object|boolean", + direction: "string", + active: "boolean", + cursor: "boolean", + blind_angle: "number", + rotation: "number", + reverse: "boolean", + limit: "boolean", + }, + options: { + range: function () { return this.parent; }, + classes: false, + get: function () { return this.parent.options.value; }, + set: function (v) { this.parent.userset("value", v); }, + events: function () { return this.parent; }, + direction: "polar", + active: true, + cursor: false, + blind_angle: 20, + rotation: 45, + reverse: false, + limit: false, + }, + /** + * Is fired while a user is dragging. + * + * @event TK.DragValue#dragging + * + * @param {DOMEvent} event - The native DOM event. + */ + /** + * Is fired when a user starts dragging. + * + * @event TK.DragValue#startdrag + * + * @param {DOMEvent} event - The native DOM event. + */ + /** + * Is fired when a user stops dragging. + * + * @event TK.DragValue#stopdrag + * + * @param {DOMEvent} event - The native DOM event. + */ + static_events: { + set_state: start_drag, + stopcapture: stop_drag, + startcapture: function() { + if (this.options.active) return true; + }, + set_rotation: function(v) { + v *= Math.PI / 180; + this.set("_direction", [ -Math.sin(v), Math.cos(v) ]); + }, + set_blind_angle: function(v) { + v *= Math.PI / 360; + this.set("_cutoff", Math.cos(v)); + }, + movecapture: movecapture, + startdrag: function(ev) { + TK.S.add(function() { + var O = this.options; + TK.add_class(O.classes || O.node, "toolkit-dragging"); + if (O.cursor) { + if (O.direction === "vertical") { + this.global_cursor("row-resize"); + } else { + this.global_cursor("col-resize"); + } + } + }.bind(this), 1); + }, + stopdrag: function() { + TK.S.add(function() { + var O = this.options; + TK.remove_class(O.classes || O.node, "toolkit-dragging"); + + if (O.cursor) { + if (O.direction === "vertical") { + this.remove_cursor("row-resize"); + } else { + this.remove_cursor("col-resize"); + } + } + }.bind(this), 1); + }, + }, + initialize: function (widget, options) { + TK.DragCapture.prototype.initialize.call(this, widget, options); + this.start_pos = 0; + var O = this.options; + this.set("rotation", O.rotation); + this.set("blind_angle", O.blind_angle); + }, +}); +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/modules/eqband.js b/share/web_surfaces/builtin/mixer/toolkit/modules/eqband.js new file mode 100644 index 0000000000..224aeeab11 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/modules/eqband.js @@ -0,0 +1,185 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function(w, TK){ + +var type_to_mode = { + parametric: "circular", + notch: "line-vertical", + lowpass1: "block-right", + lowpass2: "block-right", + lowpass3: "block-right", + lowpass4: "block-right", + highpass1: "block-left", + highpass2: "block-left", + highpass3: "block-left", + highpass4: "block-left", + "low-shelf": "line-vertical", + "high-shelf": "line-vertical", +}; + +var type_to_pref = { + parametric: ["left", "top", "right", "bottom"], + notch: ["left", "right", "top", "bottom"], + lowpass1: ["left", "top-left", "bottom-left", "right", "top-right", "bottom-right", "top", "bottom"], + lowpass2: ["left", "top-left", "bottom-left", "right", "top-right", "bottom-right", "top", "bottom"], + lowpass3: ["left", "top-left", "bottom-left", "right", "top-right", "bottom-right", "top", "bottom"], + lowpass4: ["left", "top-left", "bottom-left", "right", "top-right", "bottom-right", "top", "bottom"], + highpass1: ["right", "top-right", "bottom-right", "left", "top-left", "bottom-left", "top", "bottom"], + highpass2: ["right", "top-right", "bottom-right", "left", "top-left", "bottom-left", "top", "bottom"], + highpass3: ["right", "top-right", "bottom-right", "left", "top-left", "bottom-left", "top", "bottom"], + highpass4: ["right", "top-right", "bottom-right", "left", "top-left", "bottom-left", "top", "bottom"], + "low-shelf": ["left", "right", "top", "bottom"], + "high-shelf": ["left", "right", "top", "bottom"], +}; + +TK.EqBand = TK.class({ + /** + * An TK.EqBand extends a {@link TK.ResponseHandle} and holds a + * dependent {@link TK.Filter}. It is used as a fully functional representation + * of a single equalizer band in {@link TK.Equalizer} TK.EqBand needs a {@link TK.Chart} + * or any other derivate to be drawn in. + * + * @class TK.EqBand + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {String|Function} [options.type="parametric"] - The type of the filter. + * Possible values are parametric, notch, + * low-shelf, high-shelf, lowpass[n] or + * highpass[n]. + * @property {Number} options.freq - Frequency setting. This is an alias for the option x + * defined by {@link TK.ResponseHandle}. + * @property {Number} options.gain - Gain setting. This is an alias for the option y + * defined by {@link TK.ResponseHandle}. + * @property {Number} options.q - Quality setting. This is an alias for the option z + * defined by {@link TK.ResponseHandle}. + * + * @extends TK.ResponseHandle + */ + _class: "EqBand", + Extends: TK.ResponseHandle, + _options: Object.assign(Object.create(TK.ResponseHandle.prototype._options), { + type: "string|function", + gain: "number", + freq: "number", + x: "number", + y: "number", + z: "number", + q: "number", + }), + options: { + type: "parametric" + }, + static_events: { + set_freq: function(v) { this.set("x", v); }, + set_gain: function(v) { this.set("y", v); }, + set_q: function(v) { this.set("z", v); }, + useraction: function(k, v) { + if (k === 'x') this.set("freq", v); + if (k === 'y') this.set("gain", v); + if (k === 'z') this.set("q", v); + }, + }, + + initialize: function (options) { + /** + * @member {TK.Filter} TK.EqBand#filter - The filter providing the graphical calculations. + */ + this.filter = new TK.Filter({ + type: options.type, + }); + + TK.ResponseHandle.prototype.initialize.call(this, options); + + if (options.mode) var _m = options.mode; + this.set("type", this.options.type); + if (_m) this.set("mode", options.mode); + + if (options.x !== void(0)) + this.set("x", options.x); + else if (options.freq !== void(0)) + this.set("freq", options.freq); + if (options.y !== void(0)) + this.set("y", options.y); + else if (options.gain !== void(0)) + this.set("gain", options.gain); + if (options.z !== void(0)) + this.set("z", options.z); + else if (options.q !== void(0)) + this.set("q", options.q); + + /** + * @member {HTMLDivElement} TK.EqBand#element - The main SVG group. + * Has class toolkit-eqband. + */ + TK.add_class(this.element, "toolkit-eqband"); + + this.filter.reset(); + }, + + /** + * Calculate the gain for a given frequency in Hz. + * + * @method TK.EqBand#freq2gain + * + * @param {number} freq - The frequency. + * + * @returns {number} The gain at the given frequency. + */ + freq2gain: function (freq) { + return this.filter.get_freq2gain()(freq); + }, + + // GETTER & SETTER + set: function (key, value) { + switch (key) { + case "type": + if (typeof value === "string") { + var mode = type_to_mode[value]; + var pref = type_to_pref[value]; + if (!mode) { + TK.warn("Unsupported type:", value); + return; + } + this.set("mode", mode); + this.set("preferences", pref); + this.set("show_axis", mode === "line-vertical"); + } + this.filter.set("type", value); + break; + case "freq": + case "gain": + case "q": + value = this.filter.set(key, value); + break; + case "x": + value = this.range_x.snap(value); + break; + case "y": + value = this.range_y.snap(value); + break; + case "z": + value = this.range_z.snap(value); + break; + } + return TK.ResponseHandle.prototype.set.call(this, key, value); + } +}); +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/modules/filter.js b/share/web_surfaces/builtin/mixer/toolkit/modules/filter.js new file mode 100644 index 0000000000..c17c689dd8 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/modules/filter.js @@ -0,0 +1,401 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function(w, TK){ +/* These formulae for 'standard' biquad filter coefficients are + * from + * "Cookbook formulae for audio EQ biquad filter coefficients" + * by Robert Bristow-Johnson. + * + */ + +function Null(O) { + /* this biquad does not do anything */ + return { + b0: 1, + b1: 1, + b2: 1, + a0: 1, + a1: 1, + a2: 1, + sample_rate: O.sample_rate, + }; +} + +function LowShelf(O) { + var cos = Math.cos, + sqrt = Math.sqrt, + A = Math.pow(10, O.gain / 40), + w0 = 2*Math.PI*O.freq/O.sample_rate, + alpha = Math.sin(w0)/(2*O.q); + return { + b0: A*( (A+1) - (A-1)*cos(w0) + 2*sqrt(A)*alpha ), + b1: 2*A*( (A-1) - (A+1)*cos(w0) ), + b2: A*( (A+1) - (A-1)*cos(w0) - 2*sqrt(A)*alpha ), + a0: (A+1) + (A-1)*cos(w0) + 2*sqrt(A)*alpha, + a1: -2*( (A-1) + (A+1)*cos(w0) ), + a2: (A+1) + (A-1)*cos(w0) - 2*sqrt(A)*alpha, + sample_rate: O.sample_rate, + }; +} + +function HighShelf(O) { + var cos = Math.cos; + var sqrt = Math.sqrt; + var A = Math.pow(10, O.gain / 40); + var w0 = 2*Math.PI*O.freq/O.sample_rate; + var alpha = Math.sin(w0)/(2*O.q); + return { + b0: A*( (A+1) + (A-1)*cos(w0) + 2*sqrt(A)*alpha ), + b1: -2*A*( (A-1) + (A+1)*cos(w0) ), + b2: A*( (A+1) + (A-1)*cos(w0) - 2*sqrt(A)*alpha ), + a0: (A+1) - (A-1)*cos(w0) + 2*sqrt(A)*alpha, + a1: 2*( (A-1) - (A+1)*cos(w0) ), + a2: (A+1) - (A-1)*cos(w0) - 2*sqrt(A)*alpha, + sample_rate: O.sample_rate, + }; +} + +function Peaking(O) { + var cos = Math.cos; + var A = Math.pow(10, O.gain / 40); + var w0 = 2*Math.PI*O.freq/O.sample_rate; + var alpha = Math.sin(w0)/(2*O.q); + return { + b0: 1 + alpha*A, + b1: -2*cos(w0), + b2: 1 - alpha*A, + a0: 1 + alpha/A, + a1: -2*cos(w0), + a2: 1 - alpha/A, + sample_rate: O.sample_rate, + }; +} + +function Notch(O) { + var cos = Math.cos; + var w0 = 2*Math.PI*O.freq/O.sample_rate; + var alpha = Math.sin(w0)/(2*O.q); + return { + b0: 1, + b1: -2*cos(w0), + b2: 1, + a0: 1 + alpha, + a1: -2*cos(w0), + a2: 1 - alpha, + sample_rate: O.sample_rate, + }; +} + +/* This is a standard lowpass filter with transfer function + * H(s) = 1/(1+s) + */ +function LowPass1(O) { + var w0 = 2*Math.PI*O.freq/O.sample_rate, + s0 = Math.sin(w0), + c0 = Math.cos(w0); + return { + b0: 1-c0, + b1: 2*(1-c0), + b2: 1-c0, + a0: 1 - c0 + s0, + a1: 2*(1-c0), + a2: 1 - c0 - s0, + sample_rate: O.sample_rate, + }; +} + +function LowPass2(O) { + var cos = Math.cos; + var w0 = 2*Math.PI*O.freq/O.sample_rate; + var alpha = Math.sin(w0)/(2*O.q); + return { + b0: (1 - cos(w0))/2, + b1: 1 - cos(w0), + b2: (1 - cos(w0))/2, + a0: 1 + alpha, + a1: -2*cos(w0), + a2: 1 - alpha, + sample_rate: O.sample_rate, + }; +} + +function LowPass4(O) { + O = LowPass2(O); + O.factor = 2; + return O; +} + +/* This is a standard highpass filter with transfer function + * H(s) = s/(1+s) + */ +function HighPass1(O) { + var w0 = 2*Math.PI*O.freq/O.sample_rate, + s0 = Math.sin(w0), + c0 = Math.cos(w0); + return { + b0: s0, + b1: 0, + b2: -s0, + a0: 1 - c0 + s0, + a1: 2*(1-c0), + a2: 1 - c0 - s0, + sample_rate: O.sample_rate, + }; +} + +function HighPass2(O) { + var cos = Math.cos; + var w0 = 2*Math.PI*O.freq/O.sample_rate; + var alpha = Math.sin(w0)/(2*O.q); + return { + b0: (1 + cos(w0))/2, + b1: -(1 + cos(w0)), + b2: (1 + cos(w0))/2, + a0: 1 + alpha, + a1: -2*cos(w0), + a2: 1 - alpha, + sample_rate: O.sample_rate, + }; +} + +function HighPass4(O) { + O = HighPass2(O); + O.factor = 2; + return O; +} + +var Filters = { + Null: Null, + LowShelf: LowShelf, + HighShelf: HighShelf, + Peaking: Peaking, + Notch: Notch, + LowPass1: LowPass1, + LowPass2: LowPass2, + LowPass4: LowPass4, + HighPass1: HighPass1, + HighPass2: HighPass2, + HighPass4: HighPass4, +}; + +var standard_biquads = { + "null": BiquadFilter(Null), + "low-shelf": BiquadFilter(LowShelf), + "high-shelf": BiquadFilter(HighShelf), + parametric: BiquadFilter(Peaking), + notch: BiquadFilter(Notch), + lowpass1: BiquadFilter(LowPass1), + lowpass2: BiquadFilter(LowPass2), + lowpass3: BiquadFilter(LowPass1, LowPass2), + lowpass4: BiquadFilter(LowPass4), + highpass1: BiquadFilter(HighPass1), + highpass2: BiquadFilter(HighPass2), + highpass3: BiquadFilter(HighPass1, HighPass2), + highpass4: BiquadFilter(HighPass4), +}; + +var NullModule = { freq2gain: function(f) { return 0.0; } }; + +function BilinearModule(w, O) { + var log = Math.log; + var sin = Math.sin; + + var LN10_10 = (O.factor||1.0) * 10/Math.LN10; + var PI = +(Math.PI/O.sample_rate); + var Ra = +((O.a0 + O.a1) * (O.a0 + O.a1) / 4); + var Rb = +((O.b0 + O.b1) * (O.b0 + O.b1) / 4); + var Ya = +(O.a1 * O.a0); + var Yb = +(O.b1 * O.b0); + + if (Ra === Rb && Ya === Yb) return NullModule; + + function freq2gain(f) { + f = +f; + var S = +sin(PI * f) + S *= S; + return LN10_10 * log( (Rb - S * Yb) / + (Ra - S * Ya) ); + } + + return { freq2gain: freq2gain }; +} + +function BiquadModule(w, O) { + var log = Math.log; + var sin = Math.sin; + + var LN10_10 = (O.factor||1.0) * 10/Math.LN10; + var PI = +(Math.PI/O.sample_rate); + var Ra = +((O.a0 + O.a1 + O.a2) * (O.a0 + O.a1 + O.a2) / 4); + var Rb = +((O.b0 + O.b1 + O.b2) * (O.b0 + O.b1 + O.b2) / 4); + var Xa = +(4 * O.a0 * O.a2); + var Ya = +(O.a1 * (O.a0 + O.a2)); + var Xb = +(4 * O.b0 * O.b2); + var Yb = +(O.b1 * (O.b0 + O.b2)); + + if (Ra === Rb && Ya === Yb && Xa === Xb) return NullModule; + + function freq2gain(f) { + f = +f; + var S = +sin(PI * f) + S *= S; + return LN10_10 * log( (Rb - S * (Xb * (1 - S) + Yb)) / + (Ra - S * (Xa * (1 - S) + Ya)) ); + } + + return { freq2gain: freq2gain }; +} + +function BiquadFilter1(trafo) { + function factory(stdlib, O) { + return BiquadModule(stdlib, trafo(O)); + } + + return factory; +} + +function BiquadFilterN(trafos) { + function factory(stdlib, O) { + var A = new Array(trafos.length); + var i; + + for (i = 0; i < trafos.length; i ++) { + A[i] = BiquadModule(stdlib, trafos[i](O)).freq2gain; + } + + return { + freq2gain: function(f) { + var ret = 0.0; + var i; + + for (i = 0; i < A.length; i++) { + ret += A[i](f); + } + + return ret; + } + }; + } + + return factory; +} + +function BiquadFilter() { + if (arguments.length === 1) return BiquadFilter1(arguments[0]); + + return BiquadFilterN.call(this, Array.prototype.slice.call(arguments)); +} + +TK.BiquadFilter = BiquadFilter; + +function reset() { + this.freq2gain = null; + /** + * Is fired when a filters drawing function is reset. + * + * @event TK.Filter#reset + */ + this.fire_event("reset"); +} + +TK.Filter = TK.class({ + /** + * TK.Filter provides the math for calculating a gain from + * a given frequency for different types of biquad filters. + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {Stgring|Function} [options.type="parametric"] - The type of the filter. Possible values are + * parametric, notch, low-shelf, + * high-shelf, lowpass[n] or highpass[n]. + * @property {Number} [options.freq=1000] - The initial frequency. + * @property {Number} [options.gain=0] - The initial gain. + * @property {Number} [options.q=1] - The initial Q of the filter. + * @property {Number} [options.sample_rate=44100] - The sample rate. + * + * @mixin TK.Filter + * + * @extends TK.Base + * + * @mixes TK.AudioMath + * @mixes TK.Notes + */ + + /** + * Returns the gain for a given frequency + * + * @method TK.Filter#freq2gain + * + * @param {number} frequency - The frequency to calculate the gain for. + * + * @returns {number} gain - The gain at the given frequency. + */ + _class: "Filter", + Extends: TK.Base, + _options: { + type: "string|function", + freq: "number", + gain: "number", + q: "number", + sample_rate: "number", + }, + options: { + type: "parametric", + freq: 1000, + gain: 0, + q: 1, + sample_rate: 44100, + }, + static_events: { + set_freq: reset, + set_type: reset, + set_q: reset, + set_gain: reset, + initialized: reset, + }, + create_freq2gain: function() { + var O = this.options; + var m; + + if (typeof(O.type) === "string") { + m = standard_biquads[O.type]; + + if (!m) { + TK.error("Unknown standard filter: "+O.type); + return; + } + } else if (typeof(O.type) === "function") { + m = O.type; + } else { + TK.error("Unsupported option 'type'."); + return; + } + this.freq2gain = m(window, O).freq2gain; + }, + get_freq2gain: function() { + if (this.freq2gain === null) this.create_freq2gain(); + return this.freq2gain; + }, + reset: reset, +}); + +TK.Filters = Filters; + +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/modules/graph.js b/share/web_surfaces/builtin/mixer/toolkit/modules/graph.js new file mode 100644 index 0000000000..d0b7dae094 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/modules/graph.js @@ -0,0 +1,352 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function(w, TK) { +function range_change_cb() { + this.invalidate_all(); + this.trigger_draw(); +}; +function transform_dots(dots) { + if (dots === void(0)) return ""; + if (typeof dots === "string") return dots; + if (typeof dots === "object") { + if (Array.isArray(dots)) { + if (!dots.length || !dots[0]) return null; + var ret = { }; + var start, stop; + + for (var name in dots[0]) if (dots[0].hasOwnProperty(name)) { + var a = []; + ret[name] = a; + for (var i = 0; i < dots.length; i++) { + a[i] = dots[i][name]; + } + } + return ret; + } else return dots; + } else { + TK.error("Unsupported option 'dots':", dots); + return ""; + } +} +// this is not really a rounding operation but simply adds 0.5. we do this to make sure +// that integer pixel positions result in actual pixels, instead of being spread across +// two pixels with half opacity +function svg_round(x) { + x = +x; + return x + 0.5; +} +function svg_round_array(x) { + var i; + for (i = 0; i < x.length; i++) { + x[i] = +x[i] + 0.5; + } + return x; +} +function _start(d, s) { + var w = this.range_x.options.basis; + var h = this.range_y.options.basis; + var t = this.options.type; + var m = this.options.mode; + var x = this.range_x.val2px(d.x[0]); + var y = this.range_y.val2px(d.y[0]); + switch (m) { + case "bottom": + // fill the lower part of the graph + s.push( + "M " + svg_round(x - 1) + " ", + svg_round(h + 1) + " " + t + " ", + svg_round(x - 1) + " ", + svg_round(y) + ); + break; + case "top": + // fill the upper part of the graph + s.push("M " + svg_round(x - 1) + " " + svg_round(-1), + " " + t + " " + svg_round(x - 1) + " ", + svg_round(y) + ); + break; + case "center": + // fill from the mid + s.push( + "M " + svg_round(x - 1) + " ", + svg_round(0.5 * h) + ); + break; + case "base": + // fill from variable point + s.push( + "M " + svg_round(x - 1) + " ", + svg_round((1 - this.options.base) * h) + ); + break; + default: + TK.error("Unsupported mode:", m); + /* FALL THROUGH */ + case "line": + // fill nothing + s.push("M " + svg_round(x) + " " + svg_round(y)); + break; + } +} +function _end(d, s) { + var a = 0.5; + var h = this.range_y.options.basis; + var t = this.options.type; + var m = this.options.mode; + var x = this.range_x.val2px(d.x[d.x.length-1]); + var y = this.range_y.val2px(d.y[d.y.length-1]); + switch (m) { + case "bottom": + // fill the graph below + s.push(" " + t + " " + svg_round(x) + " " + svg_round(h + 1) + " Z"); + break; + case "top": + // fill the upper part of the graph + s.push(" " + t + " " + svg_round(x + 1) + " " + svg_round(-1) + " Z"); + break; + case "center": + // fill from mid + s.push(" " + t + " " + svg_round(x + 1) + " " + svg_round(0.5 * h) + " Z"); + break; + case "base": + // fill from variable point + s.push(" " + t + " " + svg_round(x + 1) + " " + svg_round((-m + 1) * h) + " Z"); + break; + default: + TK.error("Unsupported mode:", m); + /* FALL THROUGH */ + case "line": + // fill nothing + break; + } +} + +TK.Graph = TK.class({ + /** + * TK.Graph is a single SVG path element. It provides + * some functions to easily draw paths inside Charts and other + * derivates. + * + * @class TK.Graph + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {Function|Object} options.range_x - Callback function + * returning a {@link TK.Range} module for x axis or an object with options + * for a new {@link Range}. + * @property {Function|Object} options.range_y - Callback function + * returning a {@link TK.Range} module for y axis or an object with options + * for a new {@link Range}. + * @property {Array|String} options.dots=[] - The dots of the path. + * Can be a ready-to-use SVG-path-string or an array of objects like + * {x: x, y: y [, x1, y1, x2, y2]} (depending on the type). + * @property {String} [options.type="L"] - Type of the graph (needed values in dots object): + *
    + *
  • L: normal (needs x,y)
  • + *
  • T: smooth quadratic Bézier (needs x, y)
  • + *
  • H[n]: smooth horizontal, [n] = smoothing factor between 1 (square) and 5 (nearly no smooth)
  • + *
  • Q: quadratic Bézier (needs: x1, y1, x, y)
  • + *
  • C: CurveTo (needs: x1, y1, x2, y2, x, y)
  • + *
  • S: SmoothCurve (needs: x1, y1, x, y)
  • + *
+ * @property {String} [options.mode="line"] - Drawing mode of the graph, possible values are: + *
    + *
  • line: line only
  • + *
  • bottom: fill below the line
  • + *
  • top: fill above the line
  • + *
  • center: fill from the vertical center of the canvas
  • + *
  • base: fill from a percentual position on the canvas (set with base)
  • + *
+ * @property {Number} [options.base=0] - If mode is base set the position + * of the base line to fill from between 0 (bottom) and 1 (top). + * @property {String} [options.color=""] - Set the color of the path. + * Better use stroke and fill via CSS. + * @property {Number} [options.width=0] - The width of the graph. + * @property {Number} [options.height=0] - The height of the graph. + * @property {String|Boolean} [options.key=false] - Show a description + * for this graph in the charts key, false to turn it off. + * + * @extends TK.Widget + * + * @mixes TK.Ranges + */ + _class: "Graph", + Extends: TK.Widget, + Implements: TK.Ranges, + _options: Object.assign(Object.create(TK.Widget.prototype._options), { + dots: "array", + type: "string", + mode: "string", + base: "number", + color: "string", + range_x: "object", + range_y: "object", + width: "number", + height: "number", + key: "string|boolean", + element: void(0), + }), + options: { + dots: null, + type: "L", + mode: "line", + base: 0, + color: "", + width: 0, + height: 0, + key: false + }, + + initialize: function (options) { + TK.Widget.prototype.initialize.call(this, options); + /** @member {SVGPath} TK.Graph#element - The SVG path. Has class toolkit-graph + */ + this.element = this.widgetize(TK.make_svg("path"), true, true, true); + TK.add_class(this.element, "toolkit-graph"); + /** @member {TK.Range} TK.Graph#range_x - The range for the x axis. + */ + /** @member {TK.Range} TK.Graph#range_y - The range for the y axis. + */ + if (this.options.range_x) this.set("range_x", this.options.range_x); + if (this.options.range_y) this.set("range_y", this.options.range_y); + this.set("color", this.options.color); + this.set("mode", this.options.mode); + if (this.options.dots) this.options.dots = transform_dots(this.options.dots); + }, + + redraw: function () { + var I = this.invalid; + var O = this.options; + var E = this.element; + + if (I.color) { + I.color = false; + E.style.stroke = O.color; + } + + if (I.mode) { + I.mode = false; + TK.remove_class(E, "toolkit-filled"); + TK.remove_class(E, "toolkit-outline"); + TK.add_class(E, O.mode === "line" ? "toolkit-outline" : "toolkit-filled"); + } + + if (I.validate("dots", "type", "width", "height")) { + var a = 0.5; + var dots = O.dots; + var range_x = this.range_x; + var range_y = this.range_y; + var w = range_x.options.basis; + var h = range_y.options.basis; + + if (typeof dots === "string") { + E.setAttribute("d", dots); + } else if (!dots) { + E.setAttribute("d", ""); + } else { + var x = svg_round_array(dots.x.map(range_x.val2px)); + var y = svg_round_array(dots.y.map(range_y.val2px)); + var x1, x2, y1, y2; + // if we are drawing a line, _start will do the first point + var i = O.type === "line" ? 1 : 0; + var s = []; + var f; + + _start.call(this, dots, s); + + switch (O.type.substr(0,1)) { + case "L": + case "T": + for (; i < x.length; i++) + s.push(" " + O.type + " " + x[i] + " " + y[i]); + break; + case "Q": + case "S": + x1 = svg_round_array(dots.x1.map(range_x.val2px)); + y1 = svg_round_array(dots.y1.map(range_y.val2px)); + for (; i < x.length; i++) + s.push(" " + O.type + " " + + x1[i] + "," + y1[i] + " " + + x[i] + "," + y[i]); + break; + case "C": + x1 = svg_round_array(dots.x1.map(range_x.val2px)); + x2 = svg_round_array(dots.x2.map(range_x.val2px)); + y1 = svg_round_array(dots.y1.map(range_y.val2px)); + y2 = svg_round_array(dots.y2.map(range_y.val2px)); + for (; i < x.length; i++) + s.push(" " + O.type + " " + + x1[i] + "," + y1[i] + " " + + x2[i] + "," + y2[i] + " " + + x[i] + "," + y[i]); + break; + case "H": + f = O.type.length > 1 ? parseFloat(O.type.substr(1)) : 3; + if (i === 0) { + i++; + s.push(" S" + x[0] + "," + y[0] + " " + x[0] + "," + y[0]); + } + for (; i < x.length-1; i++) + s.push(" S" + (x[i] - Math.round(x[i] - x[i-1])/f) + "," + + y[i] + " " + x[i] + "," + y[i]); + if (i < x.length) + s.push(" S" + x[i] + "," + y[i] + " " + x[i] + "," + y[i]); + break; + default: + TK.error("Unsupported graph type", O.type); + } + + _end.call(this, dots, s); + E.setAttribute("d", s.join("")); + } + } + TK.Widget.prototype.redraw.call(this); + }, + + // GETTER & SETTER + set: function (key, value) { + if (key === "dots") { + value = transform_dots(value); + } + TK.Widget.prototype.set.call(this, key, value); + switch (key) { + case "range_x": + case "range_y": + this.add_range(value, key); + value.add_event("set", range_change_cb.bind(this)); + break; + case "width": + this.range_x.set("basis", value); + break; + case "height": + this.range_y.set("basis", value); + break; + case "dots": + /** + * Is fired when the graph changes + * @event TK.Graph#graphchanged + */ + this.fire_event("graphchanged"); + break; + } + } +}); +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/modules/grid.js b/share/web_surfaces/builtin/mixer/toolkit/modules/grid.js new file mode 100644 index 0000000000..2b3cf5925f --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/modules/grid.js @@ -0,0 +1,272 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function(w, TK) { +function draw_lines(a, mode, last) { + var labels = new Array(a.length); + var coords = new Array(a.length); + var i, label, obj; + + for (i = 0; i < a.length; i++) { + obj = a[i]; + if (obj.label) { + label = TK.make_svg("text"); + label.textContent = obj.label; + label.style["dominant-baseline"] = "central"; + TK.add_class(label, "toolkit-grid-label"); + TK.add_class(label, mode ? "toolkit-horizontal" : "toolkit-vertical"); + if (obj["class"]) TK.add_class(label, obj["class"]); + + this.element.appendChild(label); + labels[i] = label; + } + } + + var w = this.range_x.options.basis; + var h = this.range_y.options.basis; + + + TK.S.add(function() { + /* FORCE_RELAYOUT */ + + for (i = 0; i < a.length; i++) { + obj = a[i]; + label = labels[i]; + if (!label) continue; + var bb; + try { + bb = label.getBBox(); + } catch(e) { + // if we are hidden, this may throw + // we should force redraw at some later point, but + // its hard to do. the grid should really be deactivated + // by an option. + continue; + } + var tw = bb.width; + var th = bb.height; + var p = TK.get_style(label, "padding").split(" "); + if (p.length < 2) + p[1] = p[2] = p[3] = p[0]; + if (p.length < 3) { + p[2] = p[0]; + p[3] = p[1]; + } + if (p.length < 4) + p[3] = p[1]; + var pt = parseInt(p[0]) || 0; + var pr = parseInt(p[1]) || 0; + var pb = parseInt(p[2]) || 0; + var pl = parseInt(p[3]) || 0; + var x, y; + if (mode) { + y = Math.max(th / 2, Math.min(h - th / 2 - pt, this.range_y.val2px(obj.pos))); + if (y > last) continue; + x = w - tw - pl; + coords[i] = { + x : x, + y : y, + m : tw + pl + pr, + }; + last = y - th; + } else { + x = Math.max(pl, Math.min(w - tw - pl, this.range_x.val2px(obj.pos) - tw / 2)); + if (x < last) continue; + y = h-th/2-pt; + coords[i] = { + x : x, + y : y, + m : th + pt + pb, + }; + last = x + tw; + } + } + + TK.S.add(function() { + for (i = 0; i < a.length; i++) { + label = labels[i]; + if (label) { + obj = coords[i]; + if (obj) { + label.setAttribute("x", obj.x); + label.setAttribute("y", obj.y); + } else { + if (label.parentElement == this.element) + this.element.removeChild(label); + } + } + } + + for (i = 0; i < a.length; i++) { + obj = a[i]; + label = coords[i]; + var m; + if (label) m = label.m; + else m = 0; + + if ((mode && obj.pos === this.range_y.options.min) + || ( mode && obj.pos === this.range_y.options.max) + || (!mode && obj.pos === this.range_x.options.min) + || (!mode && obj.pos === this.range_x.options.max)) + continue; + var line = TK.make_svg("path"); + TK.add_class(line, "toolkit-grid-line"); + TK.add_class(line, mode ? "toolkit-horizontal" : "toolkit-vertical"); + if (obj["class"]) TK.add_class(line, obj["class"]); + if (obj.color) line.setAttribute("style", "stroke:" + obj.color); + if (mode) { + // line from left to right + line.setAttribute("d", "M0 " + Math.round(this.range_y.val2px(obj.pos)) + + ".5 L" + (this.range_x.options.basis - m) + " " + + Math.round(this.range_y.val2px(obj.pos)) + ".5"); + } else { + // line from top to bottom + line.setAttribute("d", "M" + Math.round(this.range_x.val2px(obj.pos)) + + ".5 0 L" + Math.round(this.range_x.val2px(obj.pos)) + + ".5 " + (this.range_y.options.basis - m)); + } + this.element.appendChild(line); + } + }.bind(this), 1); + }.bind(this)); +} +TK.Grid = TK.class({ + /** + * TK.Grid creates a couple of lines and labels in a SVG + * image on the x and y axis. It is used in e.g. {@link TK.Graph} and + * {@link TK.FrequencyResponse} to draw markers and values. TK.Grid needs a + * parent SVG image do draw into. The base element of a TK.Grid is a + * SVG group containing all the labels and lines. + * + * @class TK.Grid + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {Array} [options.grid_x=[]] - Array for vertical grid line definitions with the members: + * @property {Number} [options.grid_x.pos] - The value where to draw grid line and correspon ding label. + * @property {String} [options.grid_x.color] - A valid CSS color string to colorize the elements. + * @property {String} [options.grid_x.class] - A class name for the elements. + * @property {String} [options.grid_x.label] - A label string. + * @property {Array} [options.grid_y=[]] - Array for horizontal grid lines with the members: + * @property {Number} [options.grid_y.pos] - The value where to draw grid line and corresponding label. + * @property {String} [options.grid_y.color] - A valid CSS color string to colorize the elements. + * @property {String} [options.grid_y.class] - A class name for the elements. + * @property {String} [options.grid_y.label] - A label string. + * @property {Function|Object} [options.range_x={}] - A function returning + * a {@link TK.Range} instance for vertical grid lines or an object + * containing options. for a new {@link Range}. + * @property {Function|Object} [options.range_y={}] - A function returning + * a {@link TK.Range} instance for horizontal grid lines or an object + * containing options. for a new {@link Range}. + * @property {Number} [options.width=0] - Width of the grid. + * @property {Number} [options.height=0] - Height of the grid. + * + * @extends TK.Widget + * + * @mixes TK.Ranges + */ + _class: "Grid", + Extends: TK.Widget, + Implements: TK.Ranges, + _options: Object.assign(Object.create(TK.Widget.prototype._options), { + grid_x: "array", + grid_y: "array", + range_x: "object", + range_y: "object", + width: "number", + height: "number", + }), + options: { + grid_x: [], + grid_y: [], + range_x: {}, + range_y: {}, + width: 0, + height: 0 + }, + initialize: function (options) { + TK.Widget.prototype.initialize.call(this, options); + /** + * @member {SVGGroup} TK.Grid#element - The main SVG group containing all grid elements. Has class toolkit-grid. + */ + this.element = this.widgetize( + TK.make_svg("g", {"class": "toolkit-grid"}), true, true, true); + /** + * @member {TK.Range} TK.Grid#range_x - The range for the x axis. + */ + /** + * @member {TK.Range} TK.Grid#range_y - The range for the y axis. + */ + this.add_range(this.options.range_x, "range_x"); + this.add_range(this.options.range_y, "range_y"); + if (this.options.width) + this.set("width", this.options.width); + if (this.options.height) + this.set("height", this.options.width); + this.invalidate_ranges = function (key, value) { + this.invalid.range_x = true; + this.invalid.range_y = true; + this.trigger_draw(); + }.bind(this); + this.range_x.add_event("set", this.invalidate_ranges); + this.range_y.add_event("set", this.invalidate_ranges); + }, + + redraw: function () { + var I = this.invalid, O = this.options; + if (I.validate("grid_x", "grid_y", "range_x", "range_y")) { + TK.empty(this.element); + + draw_lines.call(this, O.grid_x, false, 0); + draw_lines.call(this, O.grid_y, true, this.range_y.options.basis); + } + TK.Widget.prototype.redraw.call(this); + }, + destroy: function () { + this.range_x.remove_event("set", this.invalidate_ranges); + this.range_y.remove_event("set", this.invalidate_ranges); + TK.Widget.prototype.destroy.call(this); + }, + // GETTER & SETTER + set: function (key, value) { + this.options[key] = value; + switch (key) { + case "grid_x": + case "grid_y": + /** + * Is fired when the grid has changed. + * + * @event TK.Grid#gridchanged + * + * @param {Array} grid_x - The grid elements for x axis. + * @param {Array} grid_y - The grid elements for y axis. + */ + this.fire_event("gridchanged", this.options.grid_x, this.options.grid_y); + break; + case "width": + this.range_x.set("basis", value); + break; + case "height": + this.range_y.set("basis", value); + break; + } + TK.Widget.prototype.set.call(this, key, value); + } +}); +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/modules/range.js b/share/web_surfaces/builtin/mixer/toolkit/modules/range.js new file mode 100644 index 0000000000..c167c462fb --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/modules/range.js @@ -0,0 +1,103 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function(w, TK) { +TK.Range = TK.class({ + /** + * TK.Range is used for calculating linear scales from + * different values. They are useful to build coordinate systems, + * calculate pixel positions for different scale types and the like. + * TK.Range is used e.g. in {@link TK.Scale}, {@link TK.MeterBase} and {@link TK.Graph} to draw + * elements on a certain position according to a value on an + * arbitrary scale. + * + * @class TK.Range + * + * @extends TK.Base + * + * @mixes TK.Ranged + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {String|Function} [options.scale="linear"] - Type of the value. + * linear, decibel, log2, frequency + * or a function (value, options, coef) {}. + * If a function instead of a constant is handed over, it receives the + * actual options object as the second argument and is supposed to return a + * coefficient between 0 and 1. If the third argument "coef" is true, it is + * supposed to return a value depending on a coefficient handed over as the + * first argument. + * @property {Boolean} [options.reverse=false] - true if + * the range is reversed. + * @property {Number} [options.basis=0] - Dimensions of the range, set to + * width/height in pixels, if you need it for drawing purposes, to 100 if + * you need percentual values or to 1 if you just need a linear + * coefficient for a e.g. logarithmic scale. + * @property {Number} [options.min=0] - The minimum value possible. + * @property {Number} [options.max=0] - The maximum value possible. + * @property {Number} [options.step=1] - Step size, needed for e.g. user + * interaction + * @property {Number} [options.shift_up=4] - Multiplier for e.g. SHIFT pressed + * while stepping + * @property {Number} [options.shift_down=0.25] - Multiplier for e.g. SHIFT + CONTROL + * pressed while stepping + * @property {Number|Array} [options.snap=0] - Snap the value to a virtual grid + * with this distance. Numbers define the step size between snaps, an + * array contains a list of values to snap to. + * Using snap option with float values + * causes the range to reduce its minimum and maximum values depending + * on the amount of decimal digits because of the implementation of + * math in JavaScript. Using a step size of e.g. 1.125 + * reduces the maximum usable value from 9,007,199,254,740,992 to + * 9,007,199,254,740.992 (note the decimal point). + * @property {Boolean} [options.round=false] - if snap is set, + * decide how to jump between snaps. Setting this to true + * slips to the next snap if the value is more than on its half way to it. + * Otherwise the value has to reach the next snap until it is hold there + * again. + */ + Extends : TK.Base, + _class: "Range", + Implements: [TK.Ranged], + _options: { + scale: "string|function", + reverse: "boolean", + basis: "number", + min: "number", + max: "number", + step: "number", + shift_up: "number", + shift_down: "number", + snap: "number|array", + round: "boolean", + }, + options: { + scale: "linear", + reverse: false, + basis: 0, + min: 0, + max: 0, + step: 1, + shift_up: 4, + shift_down: 0.25, + snap: 0, + round: false + }, +}); +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/modules/resize.js b/share/web_surfaces/builtin/mixer/toolkit/modules/resize.js new file mode 100644 index 0000000000..86c5470311 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/modules/resize.js @@ -0,0 +1,136 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function(w, TK){ +function dragstart(e, drag) { + var O = this.options; + if (!O.active) return; + var E = O.node; + this._xstart = e.pageX; + this._ystart = e.pageY; + this._xsize = E.offsetWidth; + this._ysize = E.offsetHeight; + this._xpos = E.offsetLeft; + this._ypos = E.offsetTop; + /** + * Is fired when resizing starts. + * + * @event TK.Resize#resizestart + * + * @param {DOMEvent} event - The native DOM event. + */ + this.fire_event("resizestart", e); +} +function dragend(e, drag) { + if (!this.options.active) return; + /** + * Is fired when resizing stops. + * + * @event TK.Resize#resizestop + * + * @param {DOMEvent} event - The native DOM event. + */ + this.fire_event("resizestop", e); +} +function dragging(e, drag) { + var O = this.options; + if (!O.active) return; + var w = this._xsize + e.pageX - this._xstart; + var h = this._ysize + e.pageY - this._ystart; + if (O.min.x >= -1) w = Math.max(O.min.x, w); + if (O.max.x >= -1) w = Math.min(O.max.x, w); + if (O.min.y >= -1) h = Math.max(O.min.y, h); + if (O.max.y >= -1) h = Math.min(O.max.y, h); + O.node.style.width = w + "px"; + O.node.style.height = h + "px"; + + /** + * Is fired when resizing is in progress. + * + * @event TK.Resize#resizing + * + * @param {DOMEvent} event - The native DOM event. + * @param {int} width - The new width of the element. + * @param {int} height - The new height of the element. + */ + this.fire_event("resizing", e, w, h); +} +function set_handle() { + var h = this.options.handle; + if (this.drag) + this.drag.destroy(); + var range = new TK.Range({}); + this.drag = new TK.DragValue(this, { node: h, + range: function () { return range; }, + onStartdrag : dragstart.bind(this), + onStopdrag : dragend.bind(this), + onDragging : dragging.bind(this) + }); +} +/** + * TK.Resize allows resizing of elements. It does that by continuously resizing an + * element while the user drags a handle. + * + * @class TK.Resize + * + * @extends TK.Base + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {HTMLElement} options.node - The element to resize. + * @property {HTMLElement} [options.handle] - A DOM node used as handle. + * If none set the element is used. + * @property {Boolean} [active=true] - Set to false to disable resizing. + * @property {Object} [options.min={x: -1, y: -1}] - Object containing x + * and y determining minimum size. A value of -1 means no minimum. + * @property {Object} [options.max={x: -1, y: -1}] - Object containing x + * and y determining maximum size. A value of -1 means no maximum. + */ +TK.Resize = TK.class({ + // TK.Resize enables resizing of elements on the screen. + _class: "Resize", + Extends: TK.Base, + _options: { + handle : "object", + active : "boolean", + min : "object", + max : "object", + node : "object" + }, + options: { + node : null, + handle : null, + active : true, + min : {x: -1, y: -1}, + max : {x: -1, y: -1} + }, + initialize: function (options) { + TK.Base.prototype.initialize.call(this, options); + this.set("handle", this.options.handle); + }, + // GETTERS & SETTERS + set: function (key, value) { + if (key === "handle") { + if (!value) value = this.options.node; + set_handle.call(this); + } + TK.Base.prototype.set.call(this, key, value); + } +}); +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/modules/responsehandle.js b/share/web_surfaces/builtin/mixer/toolkit/modules/responsehandle.js new file mode 100644 index 0000000000..ea37eaf791 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/modules/responsehandle.js @@ -0,0 +1,1354 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +"use strict"; +(function(w, TK){ +var MODES = [ + "circular", + "line-horizontal", + "line-vertical", + "block-top", + "block-bottom", + "block-left", + "block-right", + "block", +]; +function normalize(v) { + var n = Math.sqrt(v[0]*v[0] + v[1]*v[1]); + v[0] /= n; + v[1] /= n; +} +function scrollwheel(e) { + var direction; + e.preventDefault(); + var d = e.wheelDelta !== void(0) && e.wheelDelta ? e.wheelDelta : e.detail; + if (d > 0) { + direction = 1; + } else if (d < 0) { + direction = -1; + } else return; + + if (this.__sto) window.clearTimeout(this.__sto); + this.set("dragging", true); + TK.add_class(this.element, "toolkit-active"); + this.__sto = window.setTimeout(function () { + this.set("dragging", false); + TK.remove_class(this.element, "toolkit-active"); + this.fire_event("zchangeended", this.options.z); + }.bind(this), 250); + var s = this.range_z.get("step") * direction; + if (e.ctrlKey && e.shiftKey) + s *= this.range_z.get("shift_down"); + else if (e.shiftKey) + s *= this.range_z.get("shift_up"); + this.userset("z", this.get("z") + s); + if (!this._zwheel) + this.fire_event("zchangestarted", this.options.z); + this._zwheel = true; +} + +/* The following functions turn positioning options + * into somethine we can calculate with */ + +function ROT(a) { + return [ +Math.sin(+a), +Math.cos(+a) ]; +} + +var ZHANDLE_POSITION_circular = { + "top": ROT(Math.PI), + "center": [1e-10, 1e-10], + "top-right": ROT(Math.PI*3/4), + "right": ROT(Math.PI/2), + "bottom-right": ROT(Math.PI/4), + "bottom": ROT(0), + "bottom-left": ROT(Math.PI*7/4), + "left": ROT(Math.PI*3/2), + "top-left": ROT(Math.PI*5/4), +}; + +function get_zhandle_position_movable(O, X) { + var vec = ZHANDLE_POSITION_circular[O.z_handle]; + var x = (X[0]+X[2])/2; + var y = (X[1]+X[3])/2; + var R = (X[2] - X[0] - O.z_handle_size)/2; + + return [ + x + R*vec[0], + y + R*vec[1] + ]; +} + +var Z_HANDLE_SIZE_corner = [ 1, 1, 0, 0 ]; +var Z_HANDLE_SIZE_horiz = [ 1, 0, 0, 1 ]; +var Z_HANDLE_SIZE_vert = [ 0, 1, 1, 0 ]; + +function Z_HANDLE_SIZE(pos) { + switch (pos) { + default: + TK.warn("Unsupported z_handle position:", pos); + case "top-right": + case "bottom-right": + case "bottom-left": + case "top-left": + case "center": + return Z_HANDLE_SIZE_corner; + case "top": + case "bottom": + return Z_HANDLE_SIZE_vert; + case "left": + case "right": + return Z_HANDLE_SIZE_horiz; + } +}; + +function get_zhandle_size(O, X) { + var vec = Z_HANDLE_SIZE(O.z_handle); + var z_handle_size = O.z_handle_size; + var z_handle_centered = O.z_handle_centered; + var width = X[2] - X[0]; + var height = X[3] - X[1]; + + if (z_handle_centered < 1) { + width *= z_handle_centered; + height *= z_handle_centered; + } else { + width = z_handle_centered; + height = z_handle_centered; + } + + width = vec[0] * z_handle_size + vec[2] * width; + height = vec[1] * z_handle_size + vec[3] * height; + + if (width < z_handle_size) width = z_handle_size; + if (height < z_handle_size) height = z_handle_size; + + return [width, height]; +} + +var Z_HANDLE_POS = { + "top": [ 0, -1 ], + "top-right": [ 1, -1 ], + "right": [ 1, 0 ], + "bottom-right": [ 1, 1 ], + "bottom": [ 0, 1 ], + "bottom-left": [ -1, 1 ], + "left": [ -1, 0 ], + "top-left": [ -1, -1 ], + "center": [ 0, 0 ], +}; + +function get_zhandle_position(O, X, zhandle_size) { + var x = +(+X[0]+X[2]-+zhandle_size[0])/2; + var y = +(+X[1]+X[3]-+zhandle_size[1])/2; + var width = +X[2] - +X[0]; + var height = +X[3] - +X[1]; + var vec = Z_HANDLE_POS[O.z_handle] || Z_HANDLE_POS["top-right"]; + + x += +vec[0] * +(width - +zhandle_size[0])/2; + y += +vec[1] * +(height - +zhandle_size[1])/2; + + return [x, y]; +} + +function mode_to_handle(mode) { + if (mode === "block-left" || mode === "block-right" || + mode === "block-top" || mode === "block-bottom") + return "block"; + return mode; +} + +var LABEL_ALIGN = { + "line-vertical": { + "top": "middle", + "bottom": "middle", + "left": "end", + "top-left": "end", + "bottom-left":"end", + "right": "start", + "top-right":"start", + "bottom-right":"start", + "center" : "middle", + }, + "line-horizontal": { + "top": "middle", + "bottom": "middle", + "left": "start", + "top-left": "start", + "bottom-left":"start", + "right": "end", + "top-right":"end", + "bottom-right":"end", + "center" : "middle", + }, + "circular": { + "top": "middle", + "bottom": "middle", + "left": "end", + "top-left": "start", + "bottom-left":"start", + "right": "start", + "top-right":"end", + "bottom-right":"end", + "center" : "middle", + }, + "block": { + "top": "middle", + "bottom": "middle", + "left": "start", + "top-left": "start", + "bottom-left":"start", + "right": "end", + "top-right":"end", + "bottom-right":"end", + "center" : "middle", + } +} + +function get_label_align(O, pos) { + return LABEL_ALIGN[mode_to_handle(O.mode)][pos]; +} + +/* The following arrays contain multipliers, alternating x and y, starting with x. + * The first pair is a multiplier for the handle width and height + * The second pair is a multiplier for the label size + * The third pair is a multiplier for the margin +*/ + +var LABEL_POSITION = { + "line-vertical": { + top: [ 0, -1, 0, 0, 0, 1 ], + right: [ 1, 0, 0, -1/2, 1, 0 ], + left: [ -1, 0, 0, -1/2, -1, 0 ], + bottom: [ 0, 1, 0, -1, 0, -1 ], + "bottom-left": [ -1, 1, 0, -1, -1, -1 ], + "bottom-right": [ 1, 1, 0, -1, 1, -1 ], + "top-right": [ 1, -1, 0, 0, 0, 1 ], + "top-left": [ -1, -1, 0, 0, -1, 1 ], + center: [ 0, 0, 0, -1/2, 0, 0 ], + }, + "line-horizontal": { + top: [ 0, -1, 0, -1, 0, -1 ], + right: [ 1, 0, 0, -1/2, 1, 0 ], + left: [ -1, 0, 0, -1/2, -1, 0 ], + bottom: [ 0, 1, 0, 0, 0, 1 ], + "bottom-left": [ -1, 1, 0, 0, 1, 1 ], + "bottom-right": [ 1, 1, 0, 0, -1, 1 ], + "top-right": [ 1, -1, 0, -1, -1, -1 ], + "top-left": [ -1, -1, 0, -1, 1, -1 ], + center: [ 0, 0, 0, -1/2, 0, 0 ], + }, + "circular": { + top: [ 0, -1, 0, -1, 0, -1 ], + right: [ 1, 0, 0, -1/2, 1, 0 ], + left: [ -1, 0, 0, -1/2, -1, 0 ], + bottom: [ 0, 1, 0, 0, 0, 1 ], + "bottom-left": [ -1, 1, 0, 0, 0, 1 ], + "bottom-right": [ 1, 1, 0, 0, 0, 1 ], + "top-right": [ 1, -1, 0, -1, 0, -1 ], + "top-left": [ -1, -1, 0, -1, 0, -1 ], + center: [ 0, 0, 0, -1/2, 0, 0 ], + }, + "block": { + top: [ 0, -1, 0, 0, 0, 1 ], + bottom: [ 0, 1, 0, -1, 0, -1 ], + right: [ 1, 0, 0, -1/2, -1, 0 ], + left: [ -1, 0, 0, -1/2, 1, 0 ], + "bottom-left": [ -1, 1, 0, -1, 1, -1 ], + "bottom-right": [ 1, 1, 0, -1, -1, -1 ], + "top-right": [ 1, -1, 0, 0, -1, 1 ], + "top-left": [ -1, -1, 0, 0, 1, 1 ], + center: [ 0, 0, 0, -1/2, 0, 0 ], + } +} + +function get_label_position(O, X, pos, label_size) { + /* X: array containing [X0, Y0, X1, Y1] of the handle + * pos: string describing the position of the label ("top", "bottom-right", ...) + * label_size: array containing width and height of the label + */ + var m = O.margin; + + // Pivot (x, y) is the center of the handle. + var x = (X[0]+X[2])/2; + var y = (X[1]+X[3])/2; + + // Size of handle + var width = +X[2]-+X[0]; + var height = +X[3]-+X[1]; + + // multipliers + var vec = LABEL_POSITION[mode_to_handle(O.mode)][pos]; + + x += vec[0] * width/2 + vec[2] * label_size[0] + vec[4] * m; + y += vec[1] * height/2 + vec[3] * label_size[1] + vec[5] * m; + + // result is [x, y] of the "real" label position. Please note that + // the final x position depends on the LABEL_ALIGN value for pos. + // Y value is the top border of the overall label. + return [x,y]; +} + +function remove_zhandle() { + var E = this._zhandle; + if (!E) return; + this._zhandle = null; + if (this.z_drag.get("node") === E) + this.z_drag.set("node", null); + + E.remove(); +} + +function create_zhandle() { + var E; + var O = this.options; + + if (this._zhandle) remove_zhandle.call(this); + + E = TK.make_svg( + O.mode === "circular" ? "circle" : "rect", { + "class": "toolkit-z-handle", + } + ); + + this._zhandle = E; + if (this.z_drag.get("node") !== document) + this.z_drag.set("node", E); +} + +function create_line1() { + if (this._line1) remove_line1.call(this); + this._line1 = TK.make_svg("path", { + "class": "toolkit-line toolkit-line-1" + }); +} +function create_line2() { + if (this._line2) remove_line2.call(this); + this._line2 = TK.make_svg("path", { + "class": "toolkit-line toolkit-line-2" + }); +} +function remove_line1() { + if (!this._line1) return; + this._line1.remove(); + this._line1 = null; +} +function remove_line2() { + if (!this._line2) return; + this._line2.remove(); + this._line2 = null; +} + +/* Prints a line, making sure that an offset of 0.5 px aligns them on + * pixel boundaries */ +var format_line = TK.FORMAT("M %.0f.5 %.0f.5 L %.0f.5 %.0f.5"); + +/* calculates the actual label positions based on given alignment + * and dimensions */ +function get_label_dimensions(align, X, label_size) { + switch (align) { + case "start": + return [ X[0], X[1], X[0]+label_size[0], X[1]+label_size[1] ]; + case "middle": + return [ X[0]-label_size[0]/2, X[1], X[0]+label_size[0]/2, X[1]+label_size[1] ]; + case "end": + return [ X[0]-label_size[0], X[1], X[0], X[1]+label_size[1] ]; + } +} + +function redraw_handle(O, X) { + var _handle = this._handle; + + if (!O.show_handle) { + if (_handle) remove_handle.call(this); + return; + } + + var range_x = this.range_x; + var range_y = this.range_y; + var range_z = this.range_z; + + if (!range_x.options.basis || !range_y.options.basis) return; + + var x = range_x.val2px(O.x); + var y = range_y.val2px(O.y); + var z = range_z.val2coef(O.z); + + var tmp; + + if (O.mode === "circular") { + tmp = Math.max(O.min_size, z * O.max_size)/2; + X[0] = x-tmp; + X[1] = y-tmp; + X[2] = x+tmp; + X[3] = y+tmp; + + _handle.setAttribute("r", tmp.toFixed(2)); + _handle.setAttribute("cx", x.toFixed(2)); + _handle.setAttribute("cy", y.toFixed(2)); + } else if (O.mode === "block") { + tmp = Math.max(O.min_size, z)/2; + X[0] = x-tmp; + X[1] = y-tmp; + X[2] = x+tmp; + X[3] = y+tmp; + + _handle.setAttribute("x", Math.round(+X[0]).toFixed(0)); + _handle.setAttribute("y", Math.round(+X[1]).toFixed(0)); + _handle.setAttribute("width", Math.round(+X[2]-X[0]).toFixed(0)); + _handle.setAttribute("height", Math.round(+X[3]-X[1]).toFixed(0)); + } else { + var x_min = O.x_min !== false ? range_x.val2px(range_x.snap(O.x_min)) : 0; + var x_max = O.x_max !== false ? range_x.val2px(range_x.snap(O.x_max)) : range_x.options.basis; + + if (x_min > x_max) { + tmp = x_min; + x_min = x_max; + x_max = tmp; + } + + var y_min = O.y_min !== false ? range_y.val2px(range_y.snap(O.y_min)) : 0; + var y_max = O.y_max !== false ? range_y.val2px(range_y.snap(O.y_max)) : range_y.options.basis; + + if (y_min > y_max) { + tmp = y_min; + y_min = y_max; + y_max = tmp; + } + + tmp = O.min_size / 2; + + /* All other modes are drawn as rectangles */ + switch (O.mode) { + case "line-vertical": + tmp = Math.max(tmp, z * O.max_size/2); + X[0] = x-tmp; + X[1] = y_min; + X[2] = x+tmp; + X[3] = y_max; + break; + case "line-horizontal": + // line horizontal + tmp = Math.max(tmp, z * O.max_size/2); + X[0] = x_min; + X[1] = y - tmp; + X[2] = x_max; + X[3] = y + tmp; + break; + case "block-left": + // rect lefthand + X[0] = 0; + X[1] = y_min; + X[2] = Math.max(x, tmp); + X[3] = y_max; + break; + case "block-right": + // rect righthand + X[0] = x; + X[1] = y_min; + X[2] = range_x.options.basis; + X[3] = y_max; + if (X[2] - X[0] < tmp) X[0] = X[2] - tmp; + break; + case "block-top": + // rect top + X[0] = x_min; + X[1] = 0; + X[2] = x_max; + X[3] = Math.max(y, tmp); + break; + case "block-bottom": + // rect bottom + X[0] = x_min; + X[1] = y; + X[2] = x_max; + X[3] = range_y.options.basis; + if (X[3] - X[1] < tmp) X[1] = X[3] - tmp; + break; + default: + TK.warn("Unsupported mode:", O.mode); + } + + /* Draw the rectangle */ + _handle.setAttribute("x", Math.round(+X[0]).toFixed(0)); + _handle.setAttribute("y", Math.round(+X[1]).toFixed(0)); + _handle.setAttribute("width", Math.round(+X[2]-X[0]).toFixed(0)); + _handle.setAttribute("height", Math.round(+X[3]-X[1]).toFixed(0)); + } +} + +function redraw_zhandle(O, X) { + var vec; + var size; + var zhandle = this._zhandle; + + if (!O.show_handle || O.z_handle === false) { + if (zhandle) remove_zhandle.call(this); + return; + } + + if (!zhandle.parentNode) + this.element.appendChild(zhandle); + + if (this._handle && O.z_handle_below) + this.element.appendChild(this._handle); + + if (O.mode === "circular") { + /* + * position the z_handle on the circle. + */ + vec = get_zhandle_position_movable(O, X); + /* width and height are equal here */ + zhandle.setAttribute("cx", vec[0].toFixed(1)); + zhandle.setAttribute("cy", vec[1].toFixed(1)); + zhandle.setAttribute("r", (O.z_handle_size / 2).toFixed(1)); + + this.zhandle_position = vec; + } else if (O.mode === "block") { + /* + * position the z_handle on the box. + */ + vec = get_zhandle_position_movable(O, X); + size = O.z_handle_size / 2; + /* width and height are equal here */ + zhandle.setAttribute("x", vec[0].toFixed(0) - size); + zhandle.setAttribute("y", vec[1].toFixed(0) - size); + zhandle.setAttribute("width", O.z_handle_size); + zhandle.setAttribute("height", O.z_handle_size); + + this.zhandle_position = vec; + } else { + // all other handle types (lines/blocks) + this.zhandle_position = vec = get_zhandle_size(O, X); + + zhandle.setAttribute("width", vec[0].toFixed(0)); + zhandle.setAttribute("height", vec[1].toFixed(0)); + + vec = get_zhandle_position(O, X, vec); + + zhandle.setAttribute("x", vec[0].toFixed(0)); + zhandle.setAttribute("y", vec[1].toFixed(0)); + + /* adjust to the center of the zhandle */ + this.zhandle_position[0] /= 2; + this.zhandle_position[1] /= 2; + this.zhandle_position[0] += vec[0]; + this.zhandle_position[1] += vec[1]; + } + + this.zhandle_position[0] -= (X[0]+X[2])/2; + this.zhandle_position[1] -= (X[1]+X[3])/2; + normalize(this.zhandle_position); +} + +function prevent_default(e) { + e.preventDefault(); + return false; +} + +function create_label() { + var E; + this._label = E = TK.make_svg("text", { + "class": "toolkit-label" + }); + TK.add_event_listener(E, "mousewheel", this._scrollwheel); + TK.add_event_listener(E, "DOMMouseScroll", this._scrollwheel); + TK.add_event_listener(E, 'contextmenu', prevent_default); +} + +function remove_label() { + var E = this._label; + this._label = null; + E.remove(); + TK.remove_event_listener(E, "mousewheel", this._scrollwheel); + TK.remove_event_listener(E, "DOMMouseScroll", this._scrollwheel); + TK.remove_event_listener(E, 'contextmenu', prevent_default); + + this.label = [0,0,0,0]; +} + +function STOP() { return false; }; + +function create_handle() { + var O = this.options; + var E; + + if (this._handle) remove_handle.call(this); + + E = TK.make_svg(O.mode === "circular" ? "circle" : "rect", + { class: "toolkit-handle" }); + TK.add_event_listener(E, "mousewheel", this._scrollwheel); + TK.add_event_listener(E, "DOMMouseScroll", this._scrollwheel); + TK.add_event_listener(E, 'selectstart', prevent_default); + TK.add_event_listener(E, 'contextmenu', prevent_default); + this._handle = E; + this.element.appendChild(E); +} + +function remove_handle() { + var E = this._handle; + if (!E) return; + this._handle = null; + E.remove(); + TK.remove_event_listener(E, "mousewheel", this._scrollwheel); + TK.remove_event_listener(E, "DOMMouseScroll", this._scrollwheel); + TK.remove_event_listener(E, "selectstart", prevent_default); + TK.remove_event_listener(E, 'contextmenu', prevent_default); +} + +function redraw_label(O, X) { + if (!O.show_handle || O.label === false) { + if (this._label) remove_label.call(this); + return false; + } + + var a = O.label.call(this, O.title, O.x, O.y, O.z).split("\n"); + var c = this._label.childNodes; + + while (c.length < a.length) { + this._label.appendChild(TK.make_svg("tspan", {dy:"1.0em"})); + } + while (c.length > a.length) { + this._label.removeChild(this._label.lastChild); + } + for (var i = 0; i < a.length; i++) { + TK.set_text(c[i], a[i]); + } + + if (!this._label.parentNode) this.element.appendChild(this._label); + + TK.S.add(function() { + var w = 0; + for (var i = 0; i < a.length; i++) { + w = Math.max(w, c[i].getComputedTextLength()); + } + + var bbox; + + try { + bbox = this._label.getBBox(); + } catch(e) { + /* _label is not in the DOM yet */ + return; + } + + TK.S.add(function() { + var label_size = [ w, bbox.height ]; + + var i; + var pref = O.preferences; + var area = 0; + var label_position; + var text_position; + var text_anchor; + var tmp; + + /* + * Calculate possible positions of the labels and calculate their intersections. Choose + * that position which has the smallest intersection area with all other handles and labels + */ + for (i = 0; i < pref.length; i++) { + + /* get alignment */ + var align = get_label_align(O, pref[i]); + + /* get label position */ + var LX = get_label_position(O, X, pref[i], label_size); + + /* calculate the label bounding box using anchor and dimensions */ + var pos = get_label_dimensions(align, LX, label_size); + + tmp = O.intersect(pos, this); + + /* We require at least one square px smaller intersection + * to avoid flickering label positions */ + if (area === 0 || tmp.intersect + 1 < area) { + area = tmp.intersect; + label_position = pos; + text_position = LX; + text_anchor = align; + + /* there is no intersections, we are done */ + if (area === 0) break; + } + } + + this.label = label_position; + tmp = Math.round(text_position[0]) + "px"; + this._label.setAttribute("x", tmp); + this._label.setAttribute("y", Math.round(text_position[1]) + "px"); + this._label.setAttribute("text-anchor", text_anchor); + var c = this._label.childNodes; + for (var i = 0; i < c.length; i++) + c[i].setAttribute("x", tmp); + + redraw_lines.call(this, O, X); + }.bind(this), 1); + }.bind(this)); + + return true; +} + +function redraw_lines(O, X) { + + if (!O.show_handle) { + if (this._line1) remove_line1.call(this); + if (this._line2) remove_line2.call(this); + return; + } + + var pos = this.label; + var range_x = this.range_x; + var range_y = this.range_y; + var range_z = this.range_z; + + var x = range_x.val2px(O.x); + var y = range_y.val2px(O.y); + var z = range_z.val2px(O.z); + switch (O.mode) { + case "circular": + case "block": + if (O.show_axis) { + this._line1.setAttribute("d", + format_line(((y >= pos[1] && y <= pos[3]) ? Math.max(X[2], pos[2]) : X[2]) + O.margin, y, + range_x.options.basis, y)); + this._line2.setAttribute("d", + format_line(x, ((x >= pos[0] && x <= pos[2]) ? Math.max(X[3], pos[3]) : X[3]) + O.margin, + x, range_y.options.basis)); + } else { + if (this._line1) remove_line1.call(this); + if (this._line2) remove_line2.call(this); + } + break; + case "line-vertical": + case "block-left": + case "block-right": + this._line1.setAttribute("d", format_line(x, X[1], x, X[3])); + if (O.show_axis) { + this._line2.setAttribute("d", format_line(0, y, range_x.options.basis, y)); + } else if (this._line2) { + remove_line2.call(this); + } + break; + case "line-horizontal": + case "block-top": + case "block-bottom": + this._line1.setAttribute("d", format_line(X[0], y, X[2], y)); + if (O.show_axis) { + this._line2.setAttribute("d", format_line(x, 0, x, range_y.options.basis)); + } else if (this._line2) { + remove_line2.call(this); + } + break; + default: + TK.warn("Unsupported mode", pref[i]); + } + + if (this._line1 && !this._line1.parentNode) this.element.appendChild(this._line1); + if (this._line2 && !this._line2.parentNode) this.element.appendChild(this._line2); +} + +function set_main_class(O) { + var E = this.element; + var i; + + for (i = 0; i < MODES.length; i++) TK.remove_class(E, "toolkit-"+MODES[i]); + + TK.remove_class(E, "toolkit-line"); + TK.remove_class(E, "toolkit-block"); + + switch (O.mode) { + case "line-vertical": + case "line-horizontal": + TK.add_class(E, "toolkit-line"); + case "circular": + break; + case "block-left": + case "block-right": + case "block-top": + case "block-bottom": + case "block": + TK.add_class(E, "toolkit-block"); + break; + default: + TK.warn("Unsupported mode:", O.mode); + return; + } + + TK.add_class(E, "toolkit-"+O.mode); +} + +function startdrag() { + this.draw_once(function() { + var e = this.element; + var p = e.parentNode; + TK.add_class(e, "toolkit-active"); + this.set("dragging", true); + + /* TODO: move this into the parent */ + TK.add_class(this.parent.element, "toolkit-dragging"); + + this.global_cursor("move"); + + if (p.lastChild !== e) + p.appendChild(e); + }); +} + +function enddrag() { + this.draw_once(function() { + var e = this.element; + TK.remove_class(e, "toolkit-active"); + this.set("dragging", false); + + /* TODO: move this into the parent */ + TK.remove_class(this.parent.element, "toolkit-dragging"); + + this.remove_cursor("move"); + }); +} + +/** + * Class which represents a draggable SVG element, which can be used to represent and change + * a value inside of a {@link TK.ResponseHandler} and is drawn inside of a chart. + * + * @class TK.ResponseHandle + * + * @extends TK.Widget + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {Function|Object} options.range_x - Callback returning a {@link TK.Range} + * for the x-axis or an object with options for a {@link TK.Range}. This is usually + * the x_range of the parent chart. + * @property {Function|Object} options.range_y - Callback returning a {@link TK.Range} + * for the y-axis or an object with options for a {@link TK.Range}. This is usually + * the y_range of the parent chart. + * @property {Function|Object} options.range_z - Callback returning a {@link TK.Range} + * for the z-axis or an object with options for a {@link TK.Range}. + * @property {String} [options.mode="circular"] - Type of the handle. Can be one out of + * circular, line-vertical, line-horizontal, + * block-left, block-right, block-top or + * block-right. + * @property {Number} [options.x] - Value of the x-coordinate. + * @property {Number} [options.y] - Value of the y-coordinate. + * @property {Number} [options.z] - Value of the z-coordinate. + * @property {Number} [options.min_size=24] - Minimum size of the handle in px. + * @property {Number} [options.max_size=100] - Maximum size of the handle in px. + * @property {Function|Boolean} options.label - Label formatting function. Arguments are + * title, x, y, z. If set to false, no label is displayed. + * @property {Array} [options.preferences=["left", "top", "right", "bottom"]] - Possible label + * positions by order of preference. Depending on the selected mode this can + * be a subset of top, top-right, right, + * bottom-right, bottom, bottom-left, + * left, top-left and center. + * @property {Number} [options.margin=3] - Margin in px between the handle and the label. + * @property {Boolean|String} [options.z_handle=false] - If not false, a small handle is drawn at the given position (`top`, `top-left`, `top-right`, `left`, `center`, `right`, `bottom-left`, `bottom`, `bottom-right`), which can + * be dragged to change the value of the z-coordinate. + * @property {Number} [options.z_handle_size=6] - Size in px of the z-handle. + * @property {Number} [options.z_handle_centered=0.1] - Size of the z-handle in center positions. + * If this options is between 0 and 1, it is interpreted as a ratio, otherwise as a px size. + * @property {Number} [options.z_handle_below=false] - Render the z-handle below the normal handle in the DOM. SVG doesn't know CSS attribute z-index, so this workaround is needed from time to time. + * @property {Number} [options.x_min] - Minimum value of the x-coordinate. + * @property {Number} [options.x_max] - Maximum value of the x-coordinate. + * @property {Number} [options.y_min] - Minimum value of the y-coordinate. + * @property {Number} [options.y_max] - Maximum value of the y-coordinate. + * @property {Number} [options.z_min] - Minimum value of the z-coordinate. + * @property {Number} [options.z_max] - Maximum value of the z-coordinate. + * @property {Boolean} [options.show_axis=false] - If set to true, additional lines are drawn at the coordinate values. + * + * @mixes TK.Ranges + * @mixes TK.Warning + * @mixes TK.GlobalCursor + */ + +/** + * @member {SVGText} TK.ResponseHandle#_label - The label. Has class toolkit-label. + */ +/** + * @member {SVGPath} TK.ResponseHandle#_line1 - The first line. Has class toolkit-line toolkit-line-1. + */ +/** + * @member {SVGPath} TK.ResponseHandle#_line2 - The second line. Has class toolkit-line toolkit-line-2. + */ + +function set_min(value, key) { + var name = key.substr(0, 1); + var O = this.options; + if (value !== false && O[name] < value) this.set(name, value); +} + +function set_max(value, key) { + var name = key.substr(0, 1); + var O = this.options; + if (value !== false && O[name] > value) this.set(name, value); +} + +/** + * The useraction event is emitted when a widget gets modified by user interaction. + * The event is emitted for the options x, y and z. + * + * @event TK.ResponseHandle#useraction + * + * @param {string} name - The name of the option which was changed due to the users action. + * @param {mixed} value - The new value of the option. + */ +TK.ResponseHandle = TK.class({ + _class: "ResponseHandle", + Extends: TK.Widget, + Implements: [TK.GlobalCursor, TK.Ranges, TK.Warning], + _options: Object.assign(Object.create(TK.Widget.prototype._options), TK.Ranges.prototype._options, { + range_x: "mixed", + range_y: "mixed", + range_z: "mixed", + intersect: "function", + mode: "string", + preferences: "array", + label: "function|boolean", + x: "number", + y: "number", + z: "number", + min_size: "number", + max_size: "number", + margin: "number", + z_handle: "boolean|string", + z_handle_size: "number", + z_handle_centered: "number", + z_handle_below: "boolean", + min_drag: "number", + x_min: "number", + x_max: "number", + y_min: "number", + y_max: "number", + z_min: "number", + z_max: "number", + active: "boolean", + show_axis: "boolean", + title: "string", + hover: "boolean", + dragging: "boolean", + show_handle: "boolean" + }), + options: { + range_x: {}, + range_y: {}, + range_z: {}, + intersect: function () { return { intersect: 0, count: 0 } }, + // NOTE: this is currently not a public API + // callback function for checking intersections: function (x1, y1, x2, y2, id) {} + // returns a value describing the amount of intersection with other handle elements. + // intersections are weighted depending on the intersecting object. E.g. SVG borders have + // a very high impact while intersecting in comparison with overlapping handle objects + // that have a low impact on intersection + mode: "circular", + preferences: ["left", "top", "right", "bottom"], + label: TK.FORMAT("%s\n%d Hz\n%.2f dB\nQ: %.2f"), + x: 0, + y: 0, + z: 0, + min_size: 24, + max_size: 100, + margin: 3, + z_handle: false, + z_handle_size: 6, + z_handle_centered:0.1, + z_handle_below: false, + min_drag: 0, + // NOTE: not yet a public API + // amount of pixels the handle has to be dragged before it starts to move + x_min: false, + x_max: false, + y_min: false, + y_max: false, + z_min: false, + z_max: false, + active: true, + show_axis: false, + hover: false, + dragging: false, + show_handle: true + }, + static_events: { + set_show_axis: function(value) { + var O = this.options; + if (O.mode === "circular") create_line1.call(this); + create_line2.call(this); + }, + set_label: function(value) { + if (value !== false && !this._label) create_label.call(this); + }, + set_show_handle: function(value) { + this.set("mode", this.options.mode); + this.set("show_axis", this.options.show_axis); + this.set("label", this.options.label); + }, + set_mode: function(value) { + var O = this.options; + if (!O.show_handle) return; + create_handle.call(this); + if (O.z_handle !== false) create_zhandle.call(this); + if (value !== "circular") create_line1.call(this); + }, + set_x_min: set_min, + set_y_min: set_min, + set_z_min: set_min, + set_x_max: set_max, + set_y_max: set_max, + set_z_max: set_max, + mouseenter: function() { + this.set("hover", true); + }, + mouseleave: function() { + this.set("hover", false); + }, + set_active: function(v) { + if (!v) { + this.pos_drag.cancel_drag(); + this.z_drag.cancel_drag(); + } + }, + }, + + initialize: function (options) { + this.label = [0,0,0,0]; + this.handle = [0,0,0,0]; + this._zwheel = false; + this.__sto = 0; + TK.Widget.prototype.initialize.call(this, options); + var O = this.options; + + /** + * @member {TK.Range} TK.ResponseHandle#range_x - The {@link TK.Range} for the x axis. + */ + /** + * @member {TK.Range} TK.ResponseHandle#range_y - The {@link TK.Range} for the y axis. + */ + /** + * @member {TK.Range} TK.ResponseHandle#range_z - The {@link TK.Range} for the z axis. + */ + this.add_range(O.range_x, "range_x"); + this.add_range(O.range_y, "range_y"); + this.add_range(O.range_z, "range_z"); + + var set_cb = function() { + this.invalid.x = true; + this.trigger_draw(); + }.bind(this); + + this.range_x.add_event("set", set_cb); + this.range_y.add_event("set", set_cb); + this.range_z.add_event("set", set_cb); + + var E = TK.make_svg("g"); + + /** + * @member {SVGGroup} TK.ResponseHandle#element - The main SVG group containing all handle elements. Has class toolkit-response-handle. + */ + this.element = E; + + this.widgetize(E, true, true); + + TK.add_class(E, "toolkit-response-handle"); + /** + * @member {SVGCircular} TK.ResponseHandle#_handle - The main handle. + * Has class toolkit-handle. + */ + + /** + * @member {SVGCircular} TK.ResponseHandle#_zhandle - The handle for manipulating z axis. + * Has class toolkit-z-handle. + */ + + this._scrollwheel = scrollwheel.bind(this); + + this._handle = this._zhandle = this._line1 = this._line2 = this._label = null; + + this.z_drag = new TK.DragCapture(this, { + node: null, + onstartcapture: function(state) { + var self = this.parent; + var O = self.options; + if (!O.active) return; + state.z = self.range_z.val2px(O.z); + + /* the main handle is active, + * this is a z gesture */ + var pstate = self.pos_drag.state(); + if (pstate) { + var d; + var v = [ state.current.clientX - pstate.prev.clientX, + state.current.clientY - pstate.prev.clientY ]; + normalize(v); + state.vector = v; + } else { + state.vector = self.zhandle_position; + } + /** + * Is fired when the user grabs the z-handle. The argument is the + * actual z value. + * + * @event TK.ResponseHandle#zchangestarted + * + * @param {number} z - The z value. + */ + self.fire_event("zchangestarted", O.z); + startdrag.call(self); + return true; + }, + onmovecapture: function(state) { + var self = this.parent; + var O = self.options; + + var zv = state.vector; + var v = state.vdistance(); + + var d = zv[0] * v[0] + zv[1] * v[1]; + + /* ignore small movements */ + if (O.min_drag > 0 && O.min_drag > d) return; + + var range_z = self.range_z; + var z = range_z.px2val(state.z + d); + + self.userset("z", z); + }, + onstopcapture: function() { + var self = this.parent; + /** + * Is fired when the user releases the z-handle. The argument is the + * actual z value. + * + * @event TK.ResponseHandle#zchangeended + * + * @param {number} z - The z value. + */ + self.fire_event("zchangeended", self.options.z); + enddrag.call(self); + }, + }); + this.pos_drag = new TK.DragCapture(this, { + node: this.element, + onstartcapture: function(state) { + var self = this.parent; + var O = self.options; + if (!O.active) return; + + var button = state.current.button; + var E = self.element; + var p = E.parentNode; + var ev = state.current; + + self.z_drag.set("node", document); + + /* right click triggers move to the back */ + if (ev.button === 2) { + if (E !== p.firstChild) + self.draw_once(function() { + var e = this.element; + var p = e.parentNode; + if (p && e !== p.firstChild) p.insertBefore(e, p.firstChild); + }); + /* cancel everything else, but do not drag */ + ev.preventDefault(); + ev.stopPropagation(); + return false; + } + + state.x = self.range_x.val2px(O.x); + state.y = self.range_y.val2px(O.y); + /** + * Is fired when the main handle is grabbed by the user. + * The argument is an object with the following members: + *
    + *
  • x: the actual value on the x axis
  • + *
  • y: the actual value on the y axis
  • + *
  • pos_x: the position in pixels on the x axis
  • + *
  • pos_y: the position in pixels on the y axis
  • + *
+ * + * @event TK.ResponseHandle#handlegrabbed + * + * @param {Object} positions - An object containing all relevant positions of the pointer. + */ + self.fire_event("handlegrabbed", { + x: O.x, + y: O.y, + pos_x: state.x, + pos_y: state.y + }); + startdrag.call(self); + return true; + }, + onmovecapture: function(state) { + var self = this.parent; + var O = self.options; + + /* ignore small movements */ + if (O.min_drag > 0 && O.min_drag > state.distance()) return; + + /* we are changing z right now using a gesture, irgnore this movement */ + if (self.z_drag.dragging()) return; + + var v = state.vdistance(); + var range_x = self.range_x; + var range_y = self.range_y; + var x = range_x.px2val(state.x + v[0]); + var y = range_y.px2val(state.y + v[1]); + + self.userset("x", x); + self.userset("y", y); + }, + onstopcapture: function() { + /** + * Is fired when the user releases the main handle. + * The argument is an object with the following members: + *
    + *
  • x: the actual value on the x axis
  • + *
  • y: the actual value on the y axis
  • + *
  • pos_x: the position in pixels on the x axis
  • + *
  • pos_y: the position in pixels on the y axis
  • + *
+ * + * @event TK.ResponseHandle#handlereleased + * + * @param {Object} positions - An object containing all relevant positions of the pointer. + */ + var self = this.parent; + var O = self.options; + self.fire_event("handlereleased", { + x: O.x, + y: O.y, + pos_x: self.range_x.val2px(O.x), + pos_y: self.range_y.val2px(O.y), + }); + enddrag.call(self); + self.z_drag.set("node", self._zhandle); + }, + }); + + this.set("mode", O.mode); + this.set("show_handle", O.show_handle); + this.set("show_axis", O.show_axis); + this.set("active", O.active); + this.set("x", O.x); + this.set("y", O.y); + this.set("z", O.z); + this.set("z_handle", O.z_handle); + this.set("label", O.label); + + }, + + redraw: function () { + TK.Widget.prototype.redraw.call(this); + var O = this.options; + var I = this.invalid; + + var range_x = this.range_x; + var range_y = this.range_y; + var range_z = this.range_z; + + /* These are the coordinates of the corners (x1, y1, x2, y2) + * NOTE: x,y are not necessarily in the midde. */ + var X = this.handle; + + if (I.mode) set_main_class.call(this, O); + + if (I.hover) { + I.hover = false; + TK.toggle_class(this.element, "toolkit-hover", O.hover); + } + if (I.dragging) { + I.dragging = false; + TK.toggle_class(this.element, "toolkit-dragging", O.dragging); + } + + if (I.active || I.disabled) { + I.disabled = false; + // TODO: this is not very nice, we should really use the options + // for that. 1) set "active" from the mouse handlers 2) set disabled instead + // of active + TK.toggle_class(this.element, "toolkit-disabled", !O.active || O.disabled); + } + + var moved = I.validate("x", "y", "z", "mode", "active", "show_handle"); + + if (moved) redraw_handle.call(this, O, X); + + // Z-HANDLE + + if (I.validate("z_handle") || moved) { + redraw_zhandle.call(this, O, X); + } + + var delay_lines; + + // LABEL + if (I.validate("label", "title", "preference") || moved) { + delay_lines = redraw_label.call(this, O, X); + } + + // LINES + if (I.validate("show_axis") || moved) { + if (!delay_lines) redraw_lines.call(this, O, X); + } + }, + set: function(key, value) { + var O = this.options; + + switch (key) { + case "z_handle": + if (value !== false && !ZHANDLE_POSITION_circular[value]) { + TK.warn("Unsupported z_handle option:", value); + value = false; + } + if (value !== false) create_zhandle.call(this); + break; + case "x": + value = this.range_x.snap(value); + if (O.x_min !== false && value < O.x_min) value = O.x_min; + if (O.x_max !== false && value > O.x_max) value = O.x_max; + break; + case "y": + value = this.range_y.snap(value); + if (O.y_min !== false && value < O.y_min) value = O.y_min; + if (O.y_max !== false && value > O.y_max) value = O.y_max; + break; + case "z": + value = this.range_z.snap(value); + if (O.z_min !== false && value < O.z_min) { + value = O.z_min; + this.warning(this.element); + } else if (O.z_max !== false && value > O.z_max) { + value = O.z_max; + this.warning(this.element); + } + break; + } + + return TK.Widget.prototype.set.call(this, key, value); + }, + destroy: function () { + remove_zhandle.call(this); + remove_line1.call(this); + remove_line2.call(this); + remove_label.call(this); + remove_handle.call(this); + TK.Widget.prototype.destroy.call(this); + }, +}); +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/modules/scale.js b/share/web_surfaces/builtin/mixer/toolkit/modules/scale.js new file mode 100644 index 0000000000..7b8926079d --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/modules/scale.js @@ -0,0 +1,571 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function(w, TK) { +function get_base(O) { + return Math.max(Math.min(O.max, O.base), O.min); +} +function vert(O) { + return O.layout === "left" || O.layout === "right"; +} +function fill_interval(range, levels, i, from, to, min_gap, result) { + var level = levels[i]; + var x, j, pos, last_pos, last; + var diff; + + var to_pos = range.val2px(to); + last_pos = range.val2px(from); + + if (Math.abs(to_pos - last_pos) < min_gap) return; + + if (!result) result = { + values: [], + positions: [], + }; + + var values = result.values; + var positions = result.positions; + + if (from > to) level = -level; + last = from; + + for (j = ((to-from)/level)|0, x = from + level; j > 0; x += level, j--) { + pos = range.val2px(x); + diff = Math.abs(last_pos - pos); + if (Math.abs(to_pos - pos) < min_gap) break; + if (diff >= min_gap) { + if (i > 0 && diff >= min_gap * 2) { + // we have a chance to fit some more labels in + fill_interval(range, levels, i-1, + last, x, min_gap, result); + } + values.push(x); + positions.push(pos); + last_pos = pos; + last = x; + } + } + + if (i > 0 && Math.abs(last_pos - to_pos) >= min_gap * 2) { + fill_interval(range, levels, i-1, last, to, min_gap, result); + } + + return result; +} +// remove collisions from a with b given a minimum gap +function remove_collisions(a, b, min_gap, vert) { + var pa = a.positions, pb = b.positions; + var va = a.values; + var dim; + + min_gap = +min_gap; + + if (typeof vert === "boolean") + dim = vert ? b.height : b.width; + + if (!(min_gap > 0)) min_gap = 1; + + if (!pb.length) return a; + + var i, j; + var values = []; + var positions = []; + var pos_a, pos_b; + var size; + + var last_pos = +pb[0], + last_size = min_gap; + + if (dim) last_size += +dim[0] / 2; + + // If pb is just length 1, it does not matter + var direction = pb.length > 1 && pb[1] < last_pos ? -1 : 1; + + for (i = 0, j = 0; i < pa.length && j < pb.length;) { + pos_a = +pa[i]; + pos_b = +pb[j]; + size = min_gap; + + if (dim) size += dim[j] / 2; + + if (Math.abs(pos_a - last_pos) < last_size || + Math.abs(pos_a - pos_b) < size) { + // try next position + i++; + continue; + } + + if (j < pb.length - 1 && (pos_a - pos_b)*direction > 0) { + // we left the current interval, lets try the next one + last_pos = pos_b; + last_size = size; + j++; + continue; + } + + values.push(+va[i]); + positions.push(pos_a); + + i++; + } + + return { + values: values, + positions: positions, + }; +} +function create_dom_nodes(data, create) { + var nodes = []; + var values, positions; + var i; + var E = this.element; + var node; + + data.nodes = nodes; + values = data.values; + positions = data.positions; + + for (i = 0; i < values.length; i++) { + nodes.push(node = create(values[i], positions[i])); + E.appendChild(node); + } +} +function create_label(value, position) { + var O = this.options; + var elem = document.createElement("SPAN"); + elem.className = "toolkit-label"; + + if (vert(O)) { + elem.style.bottom = position.toFixed(1) + "px"; + } else { + elem.style.left = position.toFixed(1) + "px"; + } + + TK.set_content(elem, O.labels(value)); + + if (get_base(O) === value) + TK.add_class(elem, "toolkit-base"); + if (O.max === value) + TK.add_class(elem, "toolkit-max"); + if (O.min === value) + TK.add_class(elem, "toolkit-min"); + + return elem; +} +function create_dot(value, position) { + var O = this.options; + var elem = document.createElement("DIV"); + elem.className = "toolkit-dot"; + + if (O.layout === "left" || O.layout === "right") { + elem.style.bottom = Math.round(position + 0.5) + "px"; + } else { + elem.style.left = Math.round(position - 0.5) + "px"; + } + + if (get_base(O) === value) + TK.add_class(elem, "toolkit-base"); + else if (O.max === value) + TK.add_class(elem, "toolkit-max"); + else if (O.min === value) + TK.add_class(elem, "toolkit-min"); + + return elem; +} +function measure_dimensions(data) { + var nodes = data.nodes; + var width = []; + var height = []; + + for (var i = 0; i < nodes.length; i++) { + width.push(TK.outer_width(nodes[i])); + height.push(TK.outer_height(nodes[i])); + } + + data.width = width; + data.height = height; +} +function handle_end(O, labels, i) { + var node = labels.nodes[i]; + var v = labels.values[i]; + + if (v === O.min) { + TK.add_class(node, "toolkit-min"); + } else if (v === O.max) { + TK.add_class(node, "toolkit-max"); + } else return; +} +function generate_scale(from, to, include_from, show_to) { + var O = this.options; + var labels; + + if (O.show_labels || O.show_markers) + labels = { + values: [], + positions: [], + }; + + var dots = { + values: [], + positions: [], + }; + var is_vert = vert(O); + var tmp; + + if (include_from) { + tmp = this.val2px(from); + + if (labels) { + labels.values.push(from); + labels.positions.push(tmp); + } + + dots.values.push(from); + dots.positions.push(tmp); + } + + var levels = O.levels; + + fill_interval(this, levels, levels.length - 1, from, to, O.gap_dots, dots); + + if (labels) { + if (O.levels_labels) levels = O.levels_labels; + + fill_interval(this, levels, levels.length - 1, from, to, O.gap_labels, labels); + + tmp = this.val2px(to); + + if (show_to || Math.abs(tmp - this.val2px(from)) >= O.gap_labels) { + labels.values.push(to); + labels.positions.push(tmp); + + dots.values.push(to); + dots.positions.push(tmp); + } + } else { + dots.values.push(to); + dots.positions.push(this.val2px(to)); + } + + if (O.show_labels) { + create_dom_nodes.call(this, labels, create_label.bind(this)); + + if (labels.values.length && labels.values[0] === get_base(O)) { + TK.add_class(labels.nodes[0], "toolkit-base"); + } + } + + var render_cb = function() { + var markers; + + if (O.show_markers) { + markers = { + values: labels.values, + positions: labels.positions, + }; + create_dom_nodes.call(this, markers, create_dot.bind(this)); + for (var i = 0; i < markers.nodes.length; i++) + TK.add_class(markers.nodes[i], "toolkit-marker"); + } + + if (O.show_labels && labels.values.length > 1) { + handle_end(O, labels, 0); + handle_end(O, labels, labels.nodes.length-1); + } + + if (O.avoid_collisions && O.show_labels) { + dots = remove_collisions(dots, labels, O.gap_dots, is_vert); + } else if (markers) { + dots = remove_collisions(dots, markers, O.gap_dots); + } + + create_dom_nodes.call(this, dots, create_dot.bind(this)); + }; + + if (O.show_labels && O.avoid_collisions) + TK.S.add(function() { + measure_dimensions(labels); + TK.S.add(render_cb.bind(this), 3); + }.bind(this), 2); + else render_cb.call(this); +} +function mark_markers(labels, dots) { + var i, j; + + var a = labels.values; + var b = dots.values; + var nodes = dots.nodes; + + for (i = j = 0; i < a.length && j < b.length;) { + if (a[i] < b[j]) i++; + else if (a[i] > b[j]) j++; + else { + TK.add_class(nodes[j], "toolkit-marker"); + i++; + j++; + } + } +} +/** + * TK.Scale can be used to draw scales. It is used in {@link TK.MeterBase} and + * {@link TK.Fader}. TK.Scale draws labels and markers based on its parameters + * and the available space. Scales can be drawn both vertically and horizontally. + * Scale mixes in {@link TK.Ranged} and inherits all its options. + * + * @extends TK.Widget + * + * @mixes TK.Ranged + * + * @class TK.Scale + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {String} [options.layout="right"] - The layout of the TK.Scale. right and + * left are vertical layouts with the labels being drawn right and left of the scale, + * respectively. top and bottom are horizontal layouts for which the + * labels are drawn on top and below the scale, respectively. + * @property {Integer} [options.division=1] - Minimal step size of the markers. + * @property {Array} [options.levels=[1]] - Array of steps for labels and markers. + * @property {Number} [options.base=false]] - Base of the scale. If set to false it will + * default to the minimum value. + * @property {Function} [options.labels=TK.FORMAT("%.2f")] - Formatting function for the labels. + * @property {Integer} [options.gap_dots=4] - Minimum gap in pixels between two adjacent markers. + * @property {Integer} [options.gap_labels=40] - Minimum gap in pixels between two adjacent labels. + * @property {Boolean} [options.show_labels=true] - If true, labels are drawn. + * @property {Boolean} [options.show_max=true] - If true, display a label and a + * dot for the 'max' value. + * @property {Boolean} [options.show_min=true] - If true, display a label and a + * dot for the 'min' value. + * @property {Boolean} [options.show_base=true] - If true, display a label and a + * dot for the 'base' value. + * @property {Array|Boolean} [options.fixed_dots] - This option can be used to specify fixed positions + * for the markers to be drawn at. The values must be sorted in ascending order. false disables + * fixed markers. + * @property {Array|Boolean} [options.fixed_labels] - This option can be used to specify fixed positions + * for the labels to be drawn at. The values must be sorted in ascending order. false disables + * fixed labels. + * @property {Boolean} [options.show_markers=true] - If true, every dot which is located at the same + * position as a label has the toolkit-marker class set. + * @property {Number|Boolean} [options.pointer=false] - The value to set the pointers position to. Set to `false` to hide the pointer. + * @property {Number|Boolean} [options.bar=false] - The value to set the bars height to. Set to `false` to hide the bar. + */ +TK.Scale = TK.class({ + _class: "Scale", + + Extends: TK.Widget, + Implements: [TK.Ranged], + _options: Object.assign(Object.create(TK.Widget.prototype._options), TK.Ranged.prototype._options, { + layout: "string", + division: "number", + levels: "array", + levels_labels: "array", + base: "number", + labels: "function", + gap_dots: "number", + gap_labels: "number", + show_labels: "boolean", + show_min: "boolean", + show_max: "boolean", + show_base: "boolean", + fixed_dots: "boolean|array", + fixed_labels: "boolean|array", + avoid_collisions: "boolean", + show_markers: "boolean", + bar: "boolean|number", + pointer: "boolean|number", + }), + options: { + layout: "right", + division: 1, + levels: [1], + base: false, + labels: TK.FORMAT("%.2f"), + avoid_collisions: false, + gap_dots: 4, + gap_labels: 40, + show_labels: true, + show_min: true, + show_max: true, + show_base: true, + show_markers: true, + fixed_dots: false, + fixed_labels: false, + bar: false, + pointer: false, + }, + + initialize: function (options) { + var E; + TK.Widget.prototype.initialize.call(this, options); + /** + * @member {HTMLDivElement} TK.Scale#element - The main DIV element. Has class toolkit-scale + */ + if (!(E = this.element)) this.element = E = TK.element("div"); + TK.add_class(E, "toolkit-scale"); + this.element = this.widgetize(E, true, true, true); + }, + + redraw: function () { + TK.Widget.prototype.redraw.call(this); + + var I = this.invalid; + var O = this.options; + var E = this.element; + + if (I.layout) { + I.layout = false; + TK.remove_class(E, "toolkit-vertical", "toolkit-horizontal", "toolkit-top", + "toolkit-bottom", "toolkit-right", "toolkit-left"); + switch (O.layout) { + case "left": + TK.add_class(E, "toolkit-vertical", "toolkit-left"); + break; + case "right": + TK.add_class(E, "toolkit-vertical", "toolkit-right"); + break; + case "top": + TK.add_class(E, "toolkit-horizontal", "toolkit-top"); + break; + case "bottom": + TK.add_class(E, "toolkit-horizontal", "toolkit-bottom"); + break; + default: + TK.warn("Unsupported layout setting:", O.layout); + } + } + + if (I.reverse) { + /* NOTE: reverse will be validated below */ + TK.toggle_class(E, "toolkit-reverse", O.reverse); + } + + if (I.validate("base", "show_base", "gap_labels", "min", "show_min", "division", "max", "show_markers", + "fixed_dots", "fixed_labels", "levels", "basis", "scale", "reverse", "show_labels")) { + TK.empty(E); + + if (O.fixed_dots && O.fixed_labels) { + var labels; + + if (O.show_labels) { + labels = { + values: O.fixed_labels, + positions: O.fixed_labels.map(this.val2px, this), + }; + create_dom_nodes.call(this, labels, create_label.bind(this)); + } + + var dots = { + values: O.fixed_dots, + positions: O.fixed_dots.map(this.val2px, this), + }; + create_dom_nodes.call(this, dots, create_dot.bind(this)); + + if (O.show_markers && labels) { + mark_markers(labels, dots); + } + } else { + var base = get_base(O); + + if (base !== O.max) generate_scale.call(this, base, O.max, true, O.show_max); + if (base !== O.min) generate_scale.call(this, base, O.min, base === O.max, O.show_min); + } + if (this._bar) + this.element.appendChild(this._bar); + if (this._pointer) + this.element.appendChild(this._pointer); + } + }, + + resize: function () { + TK.Widget.prototype.resize.call(this); + var O = this.options; + this.set("basis", vert(O) ? TK.inner_height(this.element) + : TK.inner_width(this.element) ); + }, + + // GETTER & SETTER + set: function (key, value) { + TK.Widget.prototype.set.call(this, key, value); + switch (key) { + case "division": + case "levels": + case "labels": + case "gap_dots": + case "gap_labels": + case "show_labels": + /** + * Gets fired when an option the rendering depends on was changed + * + * @event TK.Scale#scalechanged + * + * @param {string} key - The name of the option which changed the {@link TK.Scale}. + * @param {mixed} value - The value of the option. + */ + this.fire_event("scalechanged", key, value) + break; + } + } +}); + +/** + * @member {HTMLDivElement} TK.Fader#_pointer - The DIV element of the pointer. It can be used to e.g. visualize the value set in the backend. + */ +TK.ChildElement(TK.Scale, "pointer", { + show: false, + toggle_class: true, + option: "pointer", + draw_options: Object.keys(TK.Ranged.prototype._options).concat([ "pointer", "basis" ]), + draw: function(O) { + if (this._pointer) { + var tmp = this.val2px(this.snap(O.pointer)) + "px"; + if (vert(O)) { + if (TK.supports_transform) + this._pointer.style.transform = "translateY(-"+tmp+")"; + else + this._pointer.style.bottom = tmp; + } else { + if (TK.supports_transform) + this._pointer.style.transform = "translateX("+tmp+")"; + else + this._pointer.style.left = tmp; + } + } + }, +}); + +/** + * @member {HTMLDivElement} TK.Fader#_bar - The DIV element of the bar. It can be used to e.g. visualize the value set in the backend or to draw a simple levelmeter. + */ +TK.ChildElement(TK.Scale, "bar", { + show: false, + toggle_class: true, + option: "bar", + draw_options: Object.keys(TK.Ranged.prototype._options).concat([ "bar", "basis" ]), + draw: function(O) { + if (this._bar) { + var tmp = this.val2px(this.snap(O.bar)) + "px"; + if (vert(O)) + this._bar.style.height = tmp; + else + this._bar.style.width = tmp; + } + }, +}); + +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/modules/scrollvalue.js b/share/web_surfaces/builtin/mixer/toolkit/modules/scrollvalue.js new file mode 100644 index 0000000000..6bdd58dac7 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/modules/scrollvalue.js @@ -0,0 +1,174 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function(w, TK) { +function scroll_timeout() { + /** + * Is fired when scrolling ended. + * + * @event TK.ScrollValue#scrollended + */ + fire_event.call(this, "scrollended"); + this._wheel = false; + this.__sto = false; + this.set("scrolling", false); + TK.remove_class(this.options.classes, "toolkit-scrolling"); +} +function scrollwheel(e) { + var O = this.options; + if (!O.active) return; + e.preventDefault(); + var DIR = O.scroll_direction; + var d = e.deltaX * DIR[0] + e.deltaY * DIR[1] + e.deltaZ * DIR[2]; + var direction = d > 0 ? 1 : -1; + var range = O.range.call(this); + var RO = range.options; + + var v; + + // timeout for resetting the class + if (this._wheel) { + v = this._raw_value; + window.clearTimeout(this.__sto); + } else { + this._raw_value = v = O.get.call(this); + TK.add_class(O.classes, "toolkit-scrolling"); + /** + * Is fired when scrolling starts. + * + * @event TK.ScrollValue#scrollstarted + * + * @param {DOMEvent} event - The native DOM event. + */ + fire_event.call(this, "scrollstarted", e); + this._wheel = true; + } + this.__sto = window.setTimeout(scroll_timeout.bind(this), 200); + + // calc step depending on options.step, .shift up and .shift down + var step = (RO.step || 1) * direction; + if (e.ctrlKey || e.altKey) { + step *= RO.shift_down; + } else if (e.shiftKey) { + step *= RO.shift_up; + } + + var pos = range.val2px(v); + + pos += step; + + v = range.px2val(pos); + + if (O.limit) + O.set.call(this, Math.min(RO.max, Math.max(RO.min, v))); + else + O.set.call(this, v); + + /** + * Is fired while scrolling happens. + * + * @event TK.ScrollValue#scrolling + * + * @param {DOMEvent} event - The native DOM event. + */ + fire_event.call(this, "scrolling", e); + + /* do not remember out of range values */ + if (v > RO.min && v < RO.max) + this._raw_value = v; + + return false; +} +function fire_event(title, event) { + var O = this.options; + // fire an event on this drag object and one with more + // information on the draggified element + this.fire_event(title, this, event); + var e = O.events.call(this); + if (e) e.fire_event(title, event, O.get.call(this), O.node, this, O.range.call(this)); +} +/** + * TK.ScrollValue enables the scroll wheel for setting a value of an + * object. For instance, it is used by {@link TK.Knob} to allow turning + * the knob using the scroll wheel. + * + * @class TK.ScrollValue + * + * @extends TK.Module + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {HTMLElement} options.node - The element receiving the scroll event. + * @property {Function} [options.get=function () { return this.parent.options.value; }] - Callback returning the value. + * @property {Function} [options.set=function (v) { return this.parent.userset("value", v); }] - Callback setting the value. + * @property {Function} [options.range=function () { return this.parent; }] - A function returning a {@link TK.Range} instance or options for a new one. + * @property {Function|Boolean} [options.events=false] - A function returning + * an element receiving events or false to fire events on the main element. + * @property {HTMLElement|Boolean} [options.classes=false] - Element receiving + * classes or false to set classes on the main element. + * @property {Boolean} [options.active=true] - Disable the scroll event. + * @property {Array} [options.scroll_direction=[0, -1, 0]] - An array + * containing values for x, y and z defining the direction of scrolling. + * @property {Boolean} [options.limit=false] - Limit the returned value to min and max of the range. + */ +TK.ScrollValue = TK.class({ + _class: "ScrollValue", + Extends: TK.Module, + _options: { + get: "function", + set: "function", + range: "function", + events: "function", + classes: "object|boolean", + node: "object|boolean", + active: "boolean", + scroll_direction: "array", + limit: "boolean", + }, + options: { + range: function () { return this.parent; }, + events: function () { return this.parent; }, + classes: false, + get: function () { return this.parent.options.value; }, + set: function (v) { return this.parent.userset("value", v); }, + active: true, + scroll_direction: [0, -1, 0], + limit: false, + }, + initialize: function (widget, options) { + TK.Module.prototype.initialize.call(this, widget, options); + this._wheel = false; + this._raw_value = 0.0; + this.set("node", this.options.node); + this.set("events", this.options.events); + this.set("classes", this.options.classes); + }, + static_events: { + set_node: function(value) { + this.delegate_events(value); + if (value && !this.options.classes) this.set("classes", value); + }, + wheel: scrollwheel, + }, + set: function (key, value) { + if ((key === "classes") && !value) value = this.options.node; + return TK.Module.prototype.set.call(this, key, value); + } +}) +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/polyfill/raf.js b/share/web_surfaces/builtin/mixer/toolkit/polyfill/raf.js new file mode 100644 index 0000000000..de073ab2c7 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/polyfill/raf.js @@ -0,0 +1,39 @@ +/* This file contains polyfills for some functionality not present in older browsers. */ + +// http://paulirish.com/2011/requestanimationframe-for-smart-animating/ +// http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating + +// requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel + +// MIT license + +(function() { + var lastTime = 0; + var vendors = ['ms', 'moz', 'webkit', 'o']; + for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { + window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; + window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] + || window[vendors[x]+'CancelRequestAnimationFrame']; + } + + if (!window.requestAnimationFrame) + window.requestAnimationFrame = function(callback, element) { + var currTime = new Date().getTime(); + var timeToCall = Math.max(0, 16 - (currTime - lastTime)); + var id = window.setTimeout(function() { callback(currTime + timeToCall); }, + timeToCall); + lastTime = currTime + timeToCall; + return id; + }; + + if (!window.cancelAnimationFrame) + window.cancelAnimationFrame = function(id) { + clearTimeout(id); + }; +}()); +if (!String.prototype.startsWith) { + String.prototype.startsWith = function(searchString, position){ + position = position || 0; + return this.substr(position, searchString.length) === searchString; + }; +} diff --git a/share/web_surfaces/builtin/mixer/toolkit/styles/fonts/Toolkit.css b/share/web_surfaces/builtin/mixer/toolkit/styles/fonts/Toolkit.css new file mode 100644 index 0000000000..2b44cabbfb --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/styles/fonts/Toolkit.css @@ -0,0 +1,141 @@ +@font-face { + font-family: "Toolkit"; + font-style: normal; + font-weight: normal; + src: url("Toolkit.ttf") format("truetype"); +} + +.toolkit-icon { + font-family: "Toolkit", sans-serif; + font-size: 24px; + text-align: center; + display: inline-block; + vertical-align: middle; +} + +.toolkit-button.toolkit-icon::before{ + display: table-cell; + vertical-align: middle; + text-align: center; +} + +.toolkit-icon.toolkit-12 { font-size: 12px; } +.toolkit-icon.toolkit-16 { font-size: 16px; } +.toolkit-icon.toolkit-20 { font-size: 20px; } +.toolkit-icon.toolkit-24 { font-size: 24px; } +.toolkit-icon.toolkit-32 { font-size: 32px; } +.toolkit-icon.toolkit-40 { font-size: 40px; } +.toolkit-icon.toolkit-48 { font-size: 48px; } +.toolkit-icon.toolkit-56 { font-size: 56px; } +.toolkit-icon.toolkit-64 { font-size: 64px; } +.toolkit-icon.toolkit-128 { font-size: 128px; } +.toolkit-icon.toolkit-256 { font-size: 256px; } + +.toolkit-icon.warning::before { font-family: 'Toolkit'; content: '1'; } +.toolkit-icon.information::before { font-family: 'Toolkit'; content: '2'; } +.toolkit-icon.info::before { font-family: 'Toolkit'; content: '2'; } +.toolkit-icon.error::before { font-family: 'Toolkit'; content: '3'; } +.toolkit-icon.message::before { font-family: 'Toolkit'; content: '4'; } +.toolkit-icon.close::before { font-family: 'Toolkit'; content: '5'; } +.toolkit-icon.success::before { font-family: 'Toolkit'; content: '6'; } +.toolkit-icon.hourglass::before { font-family: 'Toolkit'; content: '7'; } +.toolkit-icon.loading::before { font-family: 'Toolkit'; content: '7'; } +.toolkit-icon.questionmark::before { font-family: 'Toolkit'; content: '8'; } +.toolkit-icon.screensize::before { font-family: 'Toolkit'; content: '9'; } +.toolkit-icon.windowminimize::before { font-family: 'Toolkit'; content: 'a'; } +.toolkit-icon.winmin::before { font-family: 'Toolkit'; content: 'a'; } +.toolkit-icon.windowclose::before { font-family: 'Toolkit'; content: '0'; } +.toolkit-icon.winclose::before { font-family: 'Toolkit'; content: '0'; } +.toolkit-icon.windowmaximize::before { font-family: 'Toolkit'; content: 'b'; } +.toolkit-icon.winmax::before { font-family: 'Toolkit'; content: 'b'; } +.toolkit-icon.windowmaximizevertical::before { font-family: 'Toolkit'; content: 'c'; } +.toolkit-icon.winmaxvert::before { font-family: 'Toolkit'; content: 'c'; } +.toolkit-icon.windowmaximizehorizontal::before { font-family: 'Toolkit'; content: 'd'; } +.toolkit-icon.winmaxhoriz::before { font-family: 'Toolkit'; content: 'd'; } +.toolkit-icon.windowshrink::before { font-family: 'Toolkit'; content: 'e'; } +.toolkit-icon.winshrink::before { font-family: 'Toolkit'; content: 'e'; } +.toolkit-icon.windowresize::before { font-family: 'Toolkit'; content: 'f'; } +.toolkit-icon.unchecked::before { font-family: 'Toolkit'; content: 'g'; } +.toolkit-icon.checked::before { font-family: 'Toolkit'; content: 'h'; } +.toolkit-icon.speaker::before { font-family: 'Toolkit'; content: 'i'; } +.toolkit-icon.speakeractive::before { font-family: 'Toolkit'; content: 'i'; } +.toolkit-icon.sound::before { font-family: 'Toolkit'; content: 'i'; } +.toolkit-icon.speakermute::before { font-family: 'Toolkit'; content: 'j'; } +.toolkit-icon.mute::before { font-family: 'Toolkit'; content: 'j'; } +.toolkit-icon.gear::before { font-family: 'Toolkit'; content: 'k'; } +.toolkit-icon.cogwheel::before { font-family: 'Toolkit'; content: 'k'; } +.toolkit-icon.cog::before { font-family: 'Toolkit'; content: 'k'; } +.toolkit-icon.power::before { font-family: 'Toolkit'; content: 'l'; } +.toolkit-icon.phase::before { font-family: 'Toolkit'; content: 'm'; } +.toolkit-icon.phaseinvert::before { font-family: 'Toolkit'; content: 'm'; } +.toolkit-icon.arrowdown::before { font-family: 'Toolkit'; content: 'n'; } +.toolkit-icon.arrowup::before { font-family: 'Toolkit'; content: 'o'; } +.toolkit-icon.arrowleft::before { font-family: 'Toolkit'; content: 'p'; } +.toolkit-icon.arrowright::before { font-family: 'Toolkit'; content: 'q'; } +.toolkit-icon.hide::before { font-family: 'Toolkit'; content: 'r'; } +.toolkit-icon.show::before { font-family: 'Toolkit'; content: 's'; } +.toolkit-icon.highpass::before { font-family: 'Toolkit'; content: 't'; } +.toolkit-icon.hipass::before { font-family: 'Toolkit'; content: 't'; } +.toolkit-icon.lowpass::before { font-family: 'Toolkit'; content: 'u'; } +.toolkit-icon.lopass::before { font-family: 'Toolkit'; content: 'u'; } +.toolkit-icon.highshelf::before { font-family: 'Toolkit'; content: 'v'; } +.toolkit-icon.hishelf::before { font-family: 'Toolkit'; content: 'v'; } +.toolkit-icon.lowshelf::before { font-family: 'Toolkit'; content: 'w'; } +.toolkit-icon.loshelf::before { font-family: 'Toolkit'; content: 'w'; } +.toolkit-icon.bandpass::before { font-family: 'Toolkit'; content: 'x'; } +.toolkit-icon.bandreject::before { font-family: 'Toolkit'; content: 'y'; } +.toolkit-icon.notch::before { font-family: 'Toolkit'; content: 'z'; } +.toolkit-icon.parametric::before { font-family: 'Toolkit'; content: 'A'; } +.toolkit-icon.sine::before { font-family: 'Toolkit'; content: 'B'; } +.toolkit-icon.pulse::before { font-family: 'Toolkit'; content: 'C'; } +.toolkit-icon.sawtooth::before { font-family: 'Toolkit'; content: 'D'; } +.toolkit-icon.triangle::before { font-family: 'Toolkit'; content: 'E'; } +.toolkit-icon.bypass::before { font-family: 'Toolkit'; content: 'F'; } +.toolkit-icon.stairs::before { font-family: 'Toolkit'; content: 'G'; } +.toolkit-icon.aliased::before { font-family: 'Toolkit'; content: 'G'; } +.toolkit-icon.stereo::before { font-family: 'Toolkit'; content: 'H'; } +.toolkit-icon.mono::before { font-family: 'Toolkit'; content: 'I'; } +.toolkit-icon.zoom::before { font-family: 'Toolkit'; content: 'J'; } +.toolkit-icon.magnifier::before { font-family: 'Toolkit'; content: 'J'; } +.toolkit-icon.zoomin::before { font-family: 'Toolkit'; content: 'K'; } +.toolkit-icon.zoomout::before { font-family: 'Toolkit'; content: 'L'; } +.toolkit-icon.home::before { font-family: 'Toolkit'; content: 'M'; } +.toolkit-icon.start::before { font-family: 'Toolkit'; content: 'M'; } +.toolkit-icon.rewind::before { font-family: 'Toolkit'; content: 'N'; } +.toolkit-icon.rew::before { font-family: 'Toolkit'; content: 'N'; } +.toolkit-icon.pause::before { font-family: 'Toolkit'; content: 'O'; } +.toolkit-icon.play::before { font-family: 'Toolkit'; content: 'P'; } +.toolkit-icon.stop::before { font-family: 'Toolkit'; content: 'Q'; } +.toolkit-icon.record::before { font-family: 'Toolkit'; content: 'R'; } +.toolkit-icon.rec::before { font-family: 'Toolkit'; content: 'R'; } +.toolkit-icon.eject::before { font-family: 'Toolkit'; content: 'S'; } +.toolkit-icon.fastforward::before { font-family: 'Toolkit'; content: 'T'; } +.toolkit-icon.ffwd::before { font-family: 'Toolkit'; content: 'T'; } +.toolkit-icon.end::before { font-family: 'Toolkit'; content: 'U'; } +.toolkit-icon.repeat::before { font-family: 'Toolkit'; content: 'V'; } +.toolkit-icon.random::before { font-family: 'Toolkit'; content: 'W'; } +.toolkit-icon.day::before { font-family: 'Toolkit'; content: 'X'; } +.toolkit-icon.sun::before { font-family: 'Toolkit'; content: 'X'; } +.toolkit-icon.daylight::before { font-family: 'Toolkit'; content: 'X'; } +.toolkit-icon.light::before { font-family: 'Toolkit'; content: 'X'; } +.toolkit-icon.night::before { font-family: 'Toolkit'; content: 'Y'; } +.toolkit-icon.moon::before { font-family: 'Toolkit'; content: 'Y'; } +.toolkit-icon.dark::before { font-family: 'Toolkit'; content: 'Y'; } +.toolkit-icon.connected::before { font-family: 'Toolkit'; content: 'Z'; } +.toolkit-icon.disconnected::before { font-family: 'Toolkit'; content: 'ö'; } +.toolkit-icon.headphones::before { font-family: 'Toolkit'; content: 'ä'; } +.toolkit-icon.headphonesactive::before { font-family: 'Toolkit'; content: 'ä'; } +.toolkit-icon.headphonesmute::before { font-family: 'Toolkit'; content: 'ü'; } +.toolkit-icon.microphone::before { font-family: 'Toolkit'; content: 'Ä'; } +.toolkit-icon.mic::before { font-family: 'Toolkit'; content: 'Ä'; } +.toolkit-icon.micro::before { font-family: 'Toolkit'; content: 'Ä'; } +.toolkit-icon.microphonemute::before { font-family: 'Toolkit'; content: 'Ö'; } +.toolkit-icon.micmute::before { font-family: 'Toolkit'; content: 'Ö'; } +.toolkit-icon.micromute::before { font-family: 'Toolkit'; content: 'Ö'; } +.toolkit-icon.trash::before { font-family: 'Toolkit'; content: 'Ü'; } +.toolkit-icon.delete::before { font-family: 'Toolkit'; content: 'Ü'; } +.toolkit-icon.save::before { font-family: 'Toolkit'; content: '!'; } +.toolkit-icon.diskette::before { font-family: 'Toolkit'; content: '!'; } +.toolkit-icon.load::before { font-family: 'Toolkit'; content: '"'; } +.toolkit-icon.folder::before { font-family: 'Toolkit'; content: '"'; } +.toolkit-icon.open::before { font-family: 'Toolkit'; content: '"'; } diff --git a/share/web_surfaces/builtin/mixer/toolkit/styles/fonts/Toolkit.css.in b/share/web_surfaces/builtin/mixer/toolkit/styles/fonts/Toolkit.css.in new file mode 100644 index 0000000000..3e6868f299 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/styles/fonts/Toolkit.css.in @@ -0,0 +1,32 @@ +@font-face { + font-family: "[name]"; + font-style: normal; + font-weight: normal; + src: url("[name].ttf") format("truetype"); +} + +.[prefix]icon { + font-family: "[name]", sans-serif; + font-size: 24px; + text-align: center; + display: inline-block; + vertical-align: middle; +} + +.[prefix]button.[prefix]icon::before{ + display: table-cell; + vertical-align: middle; + text-align: center; +} + +.[prefix]icon.[prefix]12 { font-size: 12px; } +.[prefix]icon.[prefix]16 { font-size: 16px; } +.[prefix]icon.[prefix]20 { font-size: 20px; } +.[prefix]icon.[prefix]24 { font-size: 24px; } +.[prefix]icon.[prefix]32 { font-size: 32px; } +.[prefix]icon.[prefix]40 { font-size: 40px; } +.[prefix]icon.[prefix]48 { font-size: 48px; } +.[prefix]icon.[prefix]56 { font-size: 56px; } +.[prefix]icon.[prefix]64 { font-size: 64px; } +.[prefix]icon.[prefix]128 { font-size: 128px; } +.[prefix]icon.[prefix]256 { font-size: 256px; } diff --git a/share/web_surfaces/builtin/mixer/toolkit/styles/fonts/Toolkit.html b/share/web_surfaces/builtin/mixer/toolkit/styles/fonts/Toolkit.html new file mode 100644 index 0000000000..b4c759b8ff --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/styles/fonts/Toolkit.html @@ -0,0 +1,117 @@ + + + ToolkitIcons + + + + +

ToolkitIcons

+ + + + + + + +
TitleToolkitIcons
NameToolkit
FamilyToolkit
CSS-FileToolkit.css
TTF-FileToolkit.ttf
HTML<link href="Toolkit.css" rel="stylesheet" type="text/css">
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IconCharNameCSSHTML
1Warningwarning<span class='toolkit-icon warning'></span>
2Informationinformation
info
<span class='toolkit-icon information'></span>
<span class='toolkit-icon info'></span>
3Errorerror<span class='toolkit-icon error'></span>
4Messagemessage<span class='toolkit-icon message'></span>
5Closeclose<span class='toolkit-icon close'></span>
6Successsuccess<span class='toolkit-icon success'></span>
7Hourglasshourglass
loading
<span class='toolkit-icon hourglass'></span>
<span class='toolkit-icon loading'></span>
8Questionmarkquestionmark<span class='toolkit-icon questionmark'></span>
9Screen Sizescreensize<span class='toolkit-icon screensize'></span>
aWindow Minimizewindowminimize
winmin
<span class='toolkit-icon windowminimize'></span>
<span class='toolkit-icon winmin'></span>
0Window Closewindowclose
winclose
<span class='toolkit-icon windowclose'></span>
<span class='toolkit-icon winclose'></span>
bWindow Maximizewindowmaximize
winmax
<span class='toolkit-icon windowmaximize'></span>
<span class='toolkit-icon winmax'></span>
cWindow Maximize Verticalwindowmaximizevertical
winmaxvert
<span class='toolkit-icon windowmaximizevertical'></span>
<span class='toolkit-icon winmaxvert'></span>
dWindow Maximize Horizontalwindowmaximizehorizontal
winmaxhoriz
<span class='toolkit-icon windowmaximizehorizontal'></span>
<span class='toolkit-icon winmaxhoriz'></span>
eWindow Shrinkwindowshrink
winshrink
<span class='toolkit-icon windowshrink'></span>
<span class='toolkit-icon winshrink'></span>
fWindow Resizewindowresize<span class='toolkit-icon windowresize'></span>
gUncheckedunchecked<span class='toolkit-icon unchecked'></span>
hCheckedchecked<span class='toolkit-icon checked'></span>
iSpeakerspeaker
speakeractive
sound
<span class='toolkit-icon speaker'></span>
<span class='toolkit-icon speakeractive'></span>
<span class='toolkit-icon sound'></span>
jSpeaker Mutespeakermute
mute
<span class='toolkit-icon speakermute'></span>
<span class='toolkit-icon mute'></span>
kGeargear
cogwheel
cog
<span class='toolkit-icon gear'></span>
<span class='toolkit-icon cogwheel'></span>
<span class='toolkit-icon cog'></span>
lPowerpower<span class='toolkit-icon power'></span>
mPhasephase
phaseinvert
<span class='toolkit-icon phase'></span>
<span class='toolkit-icon phaseinvert'></span>
nArrow Downarrowdown<span class='toolkit-icon arrowdown'></span>
oArrow Uparrowup<span class='toolkit-icon arrowup'></span>
pArrow Leftarrowleft<span class='toolkit-icon arrowleft'></span>
qArrow Rightarrowright<span class='toolkit-icon arrowright'></span>
rHidehide<span class='toolkit-icon hide'></span>
sShowshow<span class='toolkit-icon show'></span>
tHigh Passhighpass
hipass
<span class='toolkit-icon highpass'></span>
<span class='toolkit-icon hipass'></span>
uLow Passlowpass
lopass
<span class='toolkit-icon lowpass'></span>
<span class='toolkit-icon lopass'></span>
vHigh Shelfhighshelf
hishelf
<span class='toolkit-icon highshelf'></span>
<span class='toolkit-icon hishelf'></span>
wLow Shelflowshelf
loshelf
<span class='toolkit-icon lowshelf'></span>
<span class='toolkit-icon loshelf'></span>
xBand Passbandpass<span class='toolkit-icon bandpass'></span>
yBand Rejectbandreject<span class='toolkit-icon bandreject'></span>
zNotchnotch<span class='toolkit-icon notch'></span>
AParametricparametric<span class='toolkit-icon parametric'></span>
BSinesine<span class='toolkit-icon sine'></span>
CPulsepulse<span class='toolkit-icon pulse'></span>
DSawtoothsawtooth<span class='toolkit-icon sawtooth'></span>
ETriangletriangle<span class='toolkit-icon triangle'></span>
FBypassbypass<span class='toolkit-icon bypass'></span>
GStairsstairs
aliased
<span class='toolkit-icon stairs'></span>
<span class='toolkit-icon aliased'></span>
HStereostereo<span class='toolkit-icon stereo'></span>
IMonomono<span class='toolkit-icon mono'></span>
JZoomzoom
magnifier
<span class='toolkit-icon zoom'></span>
<span class='toolkit-icon magnifier'></span>
KZoom Inzoomin<span class='toolkit-icon zoomin'></span>
LZoom Outzoomout<span class='toolkit-icon zoomout'></span>
MHomehome
start
<span class='toolkit-icon home'></span>
<span class='toolkit-icon start'></span>
NRewindrewind
rew
<span class='toolkit-icon rewind'></span>
<span class='toolkit-icon rew'></span>
OPausepause<span class='toolkit-icon pause'></span>
PPlayplay<span class='toolkit-icon play'></span>
QStopstop<span class='toolkit-icon stop'></span>
RRecordrecord
rec
<span class='toolkit-icon record'></span>
<span class='toolkit-icon rec'></span>
SEjecteject<span class='toolkit-icon eject'></span>
TFast Forwardfastforward
ffwd
<span class='toolkit-icon fastforward'></span>
<span class='toolkit-icon ffwd'></span>
UEndend<span class='toolkit-icon end'></span>
VRepeatrepeat<span class='toolkit-icon repeat'></span>
WRandomrandom<span class='toolkit-icon random'></span>
XDayday
sun
daylight
light
<span class='toolkit-icon day'></span>
<span class='toolkit-icon sun'></span>
<span class='toolkit-icon daylight'></span>
<span class='toolkit-icon light'></span>
YNightnight
moon
dark
<span class='toolkit-icon night'></span>
<span class='toolkit-icon moon'></span>
<span class='toolkit-icon dark'></span>
ZConnectedconnected<span class='toolkit-icon connected'></span>
öDisconnecteddisconnected<span class='toolkit-icon disconnected'></span>
äHeadphonesheadphones
headphonesactive
<span class='toolkit-icon headphones'></span>
<span class='toolkit-icon headphonesactive'></span>
üHeadphones Muteheadphonesmute<span class='toolkit-icon headphonesmute'></span>
ÄMicrophonemicrophone
mic
micro
<span class='toolkit-icon microphone'></span>
<span class='toolkit-icon mic'></span>
<span class='toolkit-icon micro'></span>
ÖMicrophone Mutemicrophonemute
micmute
micromute
<span class='toolkit-icon microphonemute'></span>
<span class='toolkit-icon micmute'></span>
<span class='toolkit-icon micromute'></span>
ÜTrashtrash
delete
<span class='toolkit-icon trash'></span>
<span class='toolkit-icon delete'></span>
!Savesave
diskette
<span class='toolkit-icon save'></span>
<span class='toolkit-icon diskette'></span>
"Loadload
folder
open
<span class='toolkit-icon load'></span>
<span class='toolkit-icon folder'></span>
<span class='toolkit-icon open'></span>
+ + + + + diff --git a/share/web_surfaces/builtin/mixer/toolkit/styles/fonts/Toolkit.html.in b/share/web_surfaces/builtin/mixer/toolkit/styles/fonts/Toolkit.html.in new file mode 100644 index 0000000000..e8f57bbe38 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/styles/fonts/Toolkit.html.in @@ -0,0 +1,41 @@ + + + [title] + + + + +

[title]

+ + + + + + + +
Title[title]
Name[name]
Family[family]
CSS-File[name].css
TTF-File[name].ttf
HTML<link href="[name].css" rel="stylesheet" type="text/css">
+ [glyphs] + + diff --git a/share/web_surfaces/builtin/mixer/toolkit/styles/fonts/Toolkit.svg b/share/web_surfaces/builtin/mixer/toolkit/styles/fonts/Toolkit.svg new file mode 100644 index 0000000000..f21e71ca81 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/styles/fonts/Toolkit.svg @@ -0,0 +1,849 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/share/web_surfaces/builtin/mixer/toolkit/styles/fonts/Toolkit.ttf b/share/web_surfaces/builtin/mixer/toolkit/styles/fonts/Toolkit.ttf new file mode 100644 index 0000000000..45d5423fbf Binary files /dev/null and b/share/web_surfaces/builtin/mixer/toolkit/styles/fonts/Toolkit.ttf differ diff --git a/share/web_surfaces/builtin/mixer/toolkit/styles/toolkit.css b/share/web_surfaces/builtin/mixer/toolkit/styles/toolkit.css new file mode 100644 index 0000000000..cf4df93568 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/styles/toolkit.css @@ -0,0 +1,1304 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +@import "fonts/Toolkit.css"; + +/* ############################ RESET CSS ########################### */ + +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, embed, +figure, figcaption, footer, header, hgroup, +menu, nav, output, ruby, section, summary, +time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + font-size: 1em; + font: inherit; + outline: none; +} +/* HTML5 display-role reset for older browsers */ +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display: block; +} +body { + line-height: 1; +} +ol, ul { + list-style: none; +} +blockquote, q { + quotes: none; +} +blockquote:before, blockquote:after, +q:before, q:after { + content: ''; + content: none; +} +table { + border-collapse: collapse; + border-spacing: 0; +} +pre, code { + font-family: monospace; +} + + +/* ########################### GENERIC CSS ########################## */ + +.toolkit-disabled { + opacity: 0.3; + pointer-events: none; +} + +.toolkit-inactive { + opacity: 0.3; +} + +.toolkit-widget { + contain: style layout; + display: inline-block; + vertical-align: middle; + position: relative; + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* Internet Explorer */ + -khtml-user-select: none; /* KHTML browsers (e.g. Konqueror) */ + -webkit-user-select: none; /* Chrome, Safari, and Opera */ + -webkit-touch-callout: none; /* Disable Android and iOS callouts*/ +} +.toolkit-input { + -moz-user-select: all; /* Firefox */ + -ms-user-select: all; /* Internet Explorer */ + -khtml-user-select: all; /* KHTML browsers (e.g. Konqueror) */ + -webkit-user-select: all; /* Chrome, Safari, and Opera */ + -webkit-touch-callout: all; /* Disable Android and iOS callouts*/ +} + + +/* #################### WIDGETS DEFAULT STYLES #################### */ + +/* ROOT */ + +.toolkit-root { + display: block; +} + +/* BUTTON */ + +.toolkit-button { + cursor: pointer; + display: inline-flex; + justify-content: center; + align-items: center; +} +.toolkit-button > .toolkit-label, +.toolkit-button > .toolkit-icon { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + margin: auto; + pointer-events: none; +} +.toolkit-button > .toolkit-icon { + background-position: center center; + -webkit-background-size: contain; + -moz-background-size: contain; + -ms-background-size: contain; + -o-background-size: contain; + background-size: contain; + background-repeat: no-repeat; + order: 1; + flex: 0 0 auto; +} +.toolkit-button > .toolkit-label { + order: 2; + flex: 0 0 auto; +} +.toolkit-button.toolkit-vertical { + flex-direction: column; +} + +/* BUTTONARRAY */ + +.toolkit-buttonarray { + box-sizing: border-box; + display: inline-flex; +} +.toolkit-buttonarray.toolkit-vertical { + height: 100%; + flex-direction: column; +} +.toolkit-buttonarray.toolkit-horizontal { + width: 100%; +} +.toolkit-buttonarray > .toolkit-previous, +.toolkit-buttonarray > .toolkit-next { + flex: 0 0 auto; + margin: 0; +} +.toolkit-buttonarray > .toolkit-clip { + flex: 1 1 100%; + overflow: hidden; + box-sizing: border-box; +} +.toolkit-buttonarray.toolkit-horizontal.toolkit-scroll > .toolkit-clip { + overflow-x: scroll !important; +} +.toolkit-buttonarray.toolkit-vertical.toolkit-scroll > .toolkit-clip { + overflow-y: scroll !important; +} +.toolkit-buttonarray > .toolkit-clip > .toolkit-container { + position: relative; +} +.toolkit-buttonarray.toolkit-horizontal > .toolkit-clip > .toolkit-container { + height: 100%; + white-space: nowrap; +} +.toolkit-buttonarray.toolkit-vertical > .toolkit-clip > .toolkit-container { + width: 100%; +} + +/* CONTAINER */ + +.toolkit-container.toolkit-hide { + display: none !important; +} + +/* CHART */ + +.toolkit-chart { + overflow: hidden; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.toolkit-chart > svg { + box-sizing: content-box; +} +.toolkit-chart > svg > .toolkit-key { + pointer-events: none; +} + +/* CLOCK */ + +.toolkit-widget.toolkit-clock { + -webkit-border-radius: 100%; + -moz-border-radius: 100%; + border-radius: 100%; +} + +/* COLORPICKER */ + +.toolkit-color-picker { + display: grid; + grid-template-areas: "red canvas hue" + "green canvas saturation" + "blue canvas lightness" + "cancel hex apply"; +} + +.toolkit-color-picker > .toolkit-canvas { + grid-area: canvas; + padding-top: 100%; + min-width: 360px; + position: relative; + z-index: 10; + + background-repeat: no-repeat; + -webkit-background-size: 100% 100%; + -moz-background-size: 100% 100%; + -ms-background-size: 100% 100%; + -o-background-size: 100% 100%; + background-size: 100% 100%; + background-color: black; + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAIAAADTED8xAAAtoElEQVR42u1d7Y5ku20klQ2CIAjy/g9qGIbho/zYvbPdRyJZRVKn59oeLAY9PT29XUVSHxSLUpUzXwfed8qf6OtfGv8ZpzqCX/+jEZD2vGbKc69ptek/Lf4Hwfdii1+m/8l+WOp55sWTBEE9P9uMfpKXKU/zQuLXs2S1mJR7Xv8L+XRKvoD/kwl83in9f4JZ5xFGTsCbmTDQT9DxlMHvz+h/O59Ln/vVxEC0/4ox5clffQ/8+jFfeBLz26/0f8IPqAACLb1gRp90AlByL8YMdJ6LKR/j4gw28sW5T9/Ahf4v+NmRB9k/nBI/AF/G/iFsOz1I02No3Rh4BCo+ADzkFPp/4SfV4y+4fcAQQf0FpAMcJuIcThi/fpKFB3CaL/gxkA/rI0B+dF/z9RlV7j/OP141361l5QX0/X2sdwD2AGegbn9c8edg1N+BYaGPmtVZVxiUGZF3+PXgjwAIvT/8FfgC1wHWj3z7vGps7NUNhvAdjN1aARL1Agv/FoZEIwGIXM1JsMvUMHH+AAh/+oC7fRS9BwD+qfu+bz+mRqaXyO6acR79zPcp5vciKQL4kpvy/wT+0+7w9rUEAPipc7/avcCx/pPxEC0HTnIxax+3CWoFZ40I3Ox1Lu7v8GOs85bAODp+9NGfmAJJNzjPwox8QBgM/t8C7/CU5dcAeP18OeODs97vJ+0lkMNBiIz5kxWuFQPh9pfaBMHbgARIko4pHgtsEsDfBLvvgFu+YPD1ecf1D+2ArSWQkwvaPmCZ2D35CtrHjQ9/EqVAFDoP3bLQgfnN+/WOeftAsut+4aIftHzdKZYAEBczAiZjfGMPcHsQAmUfvzy5gp6R9f0p8JZNyFofN24B/M8AcAaA148+GfAFRh52ARf8ZMBnMC/nAFvcW+j+j/ArV78X2/r4PCAGAfDw7xORRbv+6hYA82PgH0e+BoDjBbgNV9f3iHiZAW4E+LjTzywvmLuo3xKAHwRZniDEJlAYQxeeWQPgxoVgXFjbIAZ86AIniLDNvv1R4FwQ5AXGHmC1PgIr9bxl9631qeFvtb4QO2A1IqEP+a8smGaQg4mQGzhsH+zAxr0Ap2NBPsvIV0cwke/2AGqwAuImXxbaffJylrnza2bgF9vcUgV8TwKoGf0Wco1UH2rQIcRU4I8BBcCbSTBCroAvWPUAq4mnexAmUVALA1rpAMh5fBgJAjnSFqp0wLb/dqqJWSXJzowCfAZxj2Ou+4INknWHGWHavI8RANLtALd/Iw4A/N+Z8NCH/k09go1Bqx/994L2emTcCwOgz7mRF69Gvw5hrX6doWNGY8B12geeI2XsA+AL4QCGu6s3Nt6zQOrOiGlbDygAHBoucuVe9gR//ScFpx9xANzQDtj6M+JlPhT0FAvirYJWwAMLgxn5y7TPAXI7wJF85gvosKEPm49wuUfug4tJgIG5wdgHwLUbAC7M+vgmUNp2wLjfO7zsAuCKLD/sGODyAC/FcHgyLMTH/Dj17v2XDX3FrREB088CeO/EJgBTLHwFwGUM9gNggUoDwjlQYfJfWQomZnyEhTATvGEhSoOCJ2IWSuDxXPZBY0fGZeBW8ggEPgtT0gdG8vHcHYZXWNi6vkIDvzxq+a8AeMz4YRp06/rhufYWn/+kEQDXO+Lb4y0NCh+CS38FBO4GNi8/A+Bmeha5Yj4A10Tg1TBlL5i7UoiEI0ybBa8YwD4H8OuhfA6YB18B8Ap6i3hrfYXrwYAysDry9cmIgqmm6V8frMhxOgDkCOx2L3gPAAt5xRFi5D8GIuLH0Udw1x8v/YVS3seBrQOsD6xPnSilLhQFK4f5zQEUwuxYf0YPeC6seGiy+euD2W12igKNyqE1Owg6BIz9WZi847YcwO8+EPZDUK8hShcFA6PgpwO8JAFWChxGfAoEEIQY4mAn+oVJBCHGXwLAp+DrU/juQGhijDSopYoD00EgAeN3ANys7DjA1vpz95FJUTxFgWDWByi4LQH9AWALfksB01QBEcXnjI/gF8/+IAWI/fcU7DbBuC4eHA2AABBDF38xHwpURAN6gC7wYfS/BMCTFBSQt+N3wYsd8T32dzfBOHQHZfik3uHe4l2MkcH5jLUOCTm7s8PfLgAsnONRChrxA09OSdrfoYAAvwhiNJoCpbAG3DKhb9AlcoCvj7AlYL5/TFgOjqyCQGcYAGYjALZuILbRHbuzTaVc/IlT0BHRsQTAF9obBep6PGh/cyeIdYXAw8BxeuuB/jbH1uJbMtaPtto9fB7OgrDlDwwXUzfIQcwJLnjACSKQ6F8CQGQ/ErL25yjAukIIvPezHjgc6G+gr0PhFvTXAz8rWXYAJPUpGAWu9b8CYEV+e6CuD0zsQeooRPn8jxUM2wEghdxxhBDwugfwj0G1dhRgOcPr4LDjwKHBImC+fASLBmb4Z4/DWOTvAeAjF9f0VjAwyFkvQI6BbdivAXACOeoI7hJICpVQqwPcCHgPgBsHN+g39I5JbtDl/TG8AM4VQaRYmC//76dZ+AQFcylSaWEBhW2fAyhWEziMpKduljob7x+/3/YV9w26vCO+ETDfP8JcnEF2ZMD1YEhvBJCFdQx4DwCLhdUH0iwA84D2UTBiCiZjfIQF0Ph+WxSrMJzK/Vs+sM6FBu4b1i0B1sg0l48MN4jOETFsFhwilgBYzW0RcRmY1casRGvohB5gYCyMTQCsMbAl4sJO5BAirLYoiolj2EjwBwQDt9qUhIg1GvKVU8kiRAzDB7ZE2AHgoB0REdvQj7hgVYBhXfgwuNjiF28ADNEKQIQ4gyHZFYK1OxgSQviAMoiFdgbKE3CQqQA4RwcT8Ye5mCmQo4sRsiuET8NI/cPuTi8q9U+6e+3fNZ5swPAkBeAAkHK+q4QtDoCmplcQGRo7+sjGQ9biLY6BjwHSOQbUITYBxl42mzAnqantARIrQSMAprH6m3Y8TLJ5A1YADy4NEitCMgCmMQbMyL5gSGgmUqjtL7YNnDZycNwT2N03vzqQBfL3PsYmeEYcTDjAfeORA59leulAbgTAOhhMIPol2renYBe9YMQDwCwjZx3hzdfdJRCSDVY4IW6nQecCehiRcONjul2LrXQelv4PzwKFGQb8MDAcYMKmd8D7rh0dhDUeghguMHdmv3HhmF1hs/tLID1/EmzPiHPn/VsCBjACWNaHJ38HvGAlcQMYA4wAEAy8RYT/wV3wz7iAPQM44A96gSuJRG4IASti3E3w3Hn/jQAffVi7JNAqgK0Ckmw5kG4CwAG/JUKxj0YOAJVyMKQiSKEAkMgLNMLsjAdsNWilFBIrjZ62A7w+YBMhyINIE8L2BRipMWDnAJb1B7AbFPueOqYadusLlApsxDEwAZsPgAUBwmBj/F1nuMZy2LAkfGwkkRYBoQ+IS0m07tVIGYRLAhAWjACYL2jFhR2yEGISUxzcogTRyPJLMdwKO2QBnA1MwY99EJaTQ4GiIDcAbgRsYyD0e8f6TD6UYmFE0jArEnYW37IQrgLFZoFcCOakgZTldwHAGj/HgpMG7RXEYifBcwE9bOsj0EmnD6Nf+EmQYsR1gI9ycZ4IXxEfMpJjIUyDtkSFYj6gZmOAQz4Q+UNjPwxwPMCQU6eiDGY9aWqFSiGKyCUd91FblJz1h6uKWxRRwyagcRWgDesfcBWEKMKNAHCQh6vAcC1IZgAaF3+2F6zIbz8qPwyETozUAonrVolN8PDq4R3Td21/JR4Ekd1gfR+8S4Nu8ad3wOgOEB0AWpoiRFkgZwzAN8ECEOEfhKU7hOZE8e8BIC+dggeTAMSTYfAkkE4DhrB1PwP4pqdSIIUEMJv9TujihxkAIexEGjQgAmiLwmZCSWn47X6AsXsMHoPiR0BwTlyjQ8EE7OEFgLj4keMwAVLhgM1Z+yd08S9dIUYWNuUCCmSBrFFPyPuBEFH82ATAjYwbB+AqKCwISJUCHJOGpzH7FBSqQXJXJOTsv8R9lwsochgOXJAhjPWpGrA/AmAu92LpwgE19vuuD5fD5C5JgeXw1ipg9YGb6SccA3A9oGA3xCR6hEf1kNsAsFwggT8oh7JrgU7XwupbAMjuikBdcIM1UJKviGZvCsMlAXZfgK3H5/BrJO2Ax36/rrIIfpkBpnFHah1/MPq59wSnpUE+br2nQbchbzlDQhel9D6YUoPU8L/6gENBC37l8J8AvxPEnMbvuYB7EHZImbpbAqWBdgsjH6DAHgQPUfCdwGsA/mkXKIjiv0gapX/hTelHLw4vf5Xxb32A+vfRr0+Dr+I3rkk9POSBAXB9YELQT1DwXfB/J/DnKHDSoNpHDNwbaQ2Aa7f3ufoMreYx6CH8kTIWwd9Ige6LwrU1MLLg59IY7Sx+ozeo8G3RkEzgCPYAlw16wDSk9n5HGwQOKBVu3Rd/c4aLsbvfIYFPBRTzf8Ae4LLjvoLfTIdF9wSzmcBBq6O/AmBr+rFzg4s8CiBPA6SpKcBAj4Osm9K3cR/iFww/dgJQPwQAmuM6+FcXyOH3a4EOdcXWuDn8awAM2/VBDoQRRcNHwumTUEULArbWdzCfxN8FHuyIYEf/6g5gDHB0RIqweiFM2BVA97eDr26Aj/1IOVBUJADCztXDueUw25vScbvjonDliGDngRFfD7M9CLNgh1yAsEM9QFc9HFYKeSuFsKDjPiC0IhwvA0xUwwJEWE5/uyfZJyKsg+XbYzSWgip6EuwT0VIJF3aGq0ujEUW0bgLgelcCXLbFw1WAAIpwsiI6p4fAKoLHzg22VySC8yBfC326KYC7Adpa3iei0hFBgVqghCKIEoXvZgDnanifgFALBYhjQjVIThE3YpGgg3zuiKAUURFyRwUXiuIoORyw/rtcR7iNfo4jSOQIuCKsSwqr3h7AuQeZMn2HFvwxIfwuANbvh0gpcNHFiG4CoAszgdwWxDyljv7SBG+/H4qHjqiQtqiY5QFADvr6AZtbM8AHzP5+EEaJwnMdYTQOgFfEI5oIq31hYmc41xHpPQB8H1DGDcosPKCINwLgeueidyG88XVjD9DbIszVRb8GwOr0le2vVre/h1rkLYPB1ujU3rcvCYC0A6hshW1J5HbEA1kQnoWwGjTRGRdsD7qbAW5wrQfs6g/OhbC9AIRPANo+YLn+YPLBgqWB+R6RuW0gkwO+zYAjSv06vkCroaM0KHUExh6HvATAin64Z4DhFFhIgudE4b7ru2dhPvjEaQishWfPQZBTULJH+NyB970AWQX7x2HUBRmsLn5wuuivANii33pC+vib7AlSqQwZRDXIY+A1E/Qnkd8CIAQ/4DoQBcHv7ggr1oA5HGw7g6mHfgv69qOk7obRU6JwsinAzwAYixsoxkVHXwDlWwN03RPzcgzugx/wZpCrCrQ1waw0fkSlscPbA1joV9CjryK4CTlbEb24wRr9deSa7wtwrhBcg0OQAYwB/uKHRu5ugtmScOqadN0HgGX6Ee19BNZEwLooUBbC3o+o+xlgHelD5CEj8DCgkSQCHwmYa+JvhyAI7IGN/QLqo2BNcIsAbveybQAgTo8bPasRZ0+/s9TkMFeoIQGf8IXlEOTC/q4Z849xSAcK82EFwEebQSRcpaALz/3d9Rzgk7LgWfPy6j+yK4Q2h/9XAKQnkgNhoKktRusMkEN4gA49MPcvfYGwa1TO2JzsC4TvBAYxAyiJ26cE0bZ2rIQT/hDtAZA/LXoC2RgD3wYNMirUCwDKqxDvpAIA6Qwm2eTH8uSlkKdoKv/BpECQnaMezAIpsGVKzwmYIh4xu5DRH3XHnECuCCECsTySBcLl8Lk88Gp99QjwfapPDg/qwhEWwHOA94MwP2XosCC59Dd3DtJ1AqDBHsBvp8IaP9Eb9JA63j0YfA0Aiy8221JTh7ewALcHX/cAIQsHYH/C8rcA+IzxsfsB0hUxI3aArwCw1NMK0FBQxB9CztDBIh+wDwhNR0UXn7ojY0owAIyjyFM3xLClkG6J4FCTAMrorCIergllBeL8FUkDq5/UaO+fFcVrqy4eLoR+DYAQ8ACGgUw7gOiOMB+3NBAwNHB6PBg6FPFpLkaBAn4ASFBwWA8BWm1XDm2niDizZ7gANMG4PC4lCrP2AAO2uxR10Z2i+JQ6agAyemolzIji0+0QKHUYsAdw7D/c5gua6ojgbIJDJyoKYoeXBfKFtAkJNCkJBN8DWQXB+lhrBMQpkDYKcPxdmnA5aP9YHQ+cBLOHP2SThNsmGPz+YEFEO/hoD9BLwYNFECn8E/b+I/ZfskBI/CQmfycJrs4pSSw4y00CGg98FAUD9gf4HKCLAqxDgtaGfwR/dBJMUUDNhh74pTkusgEQMgXgisLXPYC/hao0hkp1RcAXwAkKliWQRgtgXAoDrYGDkaDSHAXrDTHtoSFNAbgNUAl6g4ItAUAtvEInwX4aMJf9VLj4ARbFs30BbFhzBDnQyiEAwEVITpoIIP9lbYJBCgYWAx4FUWtEJ/zxQdBPgiua+M6NA3AyEO8M3IT8lwMMYgDoQl47BKmfAmqwB0gjx3uDWOcAiQNAqkv8gA7CfHl9oj06r4tHzoMFuxzAvyBkBEUQ4f/QhzwBG+mQH90PEJ7+UgURXFUAcEMMWxoygIqQqBQih75WAlSviEEKYTSYAbRcFPPRQijE8hoLYkJPaoNt1AJRFZCOIh4oB75tgvGCQLwrAlwTWqkDdSojh8fCHHHdeOIsEKwJrdWBUo0x7DvCwnLwYXhYjgWnFghUlCiTCYsEIdYSKKEH8C0n3FQQKqwpcYiTBhxeplBTIyDfGAMUEiXUQKEgCtAO4CJsUNIDCmJCfQTYD0ChJVC7MhiU+pAHQcKMg4DU5zULBP4pJQjiFfG9mmBYFE95D275gIvuk2BeIz4Uf+0hrSjlKd3q8Dma2wEUnf2UnakAaBwMezXBB4aCnyfB36P1w2GoeBr0xIR4hrUyTfMwVFwTjP9VzgGMF6h+f6NX1r3hGpjpCsGS8r1HAjX3AFpbBeJLWeemeOFlAPwO0DkJruyDU83RwN4IWk4CMGlQdhOoGAhyZZT2e7gz3KE8gIYn3btaoHQWJPTcER+EhQwea4mApAHxYjC4KYB1EJZIAyK5oFQiiJ37mQ6xE0A+OpDvHcGVRLL9EPwTEUAPgMjB2ZKw8iGA8AJxRho+R5soHMn980cBifKXlCje8YKwGhQ/Bwk3wSf6w7sEgKL4elcEfv3jgKc0wS4R6zlAog4k3x8/Rn7YBSajiG/xglARluiNABav7eqdQlE8WBAJloBIXBYJ1kVpHrO/B8DBD6wxggQlYYrVBSnpAmAxYBYzCD6oh/sxkJpBqiUAXAgdVoMmaoHTpdFwKWSiLtxlYY58ObAyH5MpBEq3hOApmFgpLFsMAFbDKnBTPK4GGYAsAmuMFUohuhtDKM9CkxpmAl0his1hBNLFt7SE4CkIFWE4C8KzAN4Un2iM4BgyJYr3RWFSaQwQhEGxH4IGer+vYri6JDBkAZ4EtEDBwCiANcGsIJiTA+4uym7sAaBEKURdCI3JfxONIRq5wPYAFS6kjYscBaRTTOi47BgXUSlEkQZgKABF8b3xoMFaSE86ALYEegrzA4AxUXy9+0Um7uGb4nFVJNYMansQRs18g5//NL/+aVwCwJvgxlUgvApSfgk8Cl5gH4ThjiCV9lj2RdmJfSCyCVaoGjTRGALZAfGJoHP7YOMc4MQOmEwBFZMgTFPIUBOsPBFcHiTaBFc6w3akQVnr81p4AfJl9ebAtSxQMRnKSONb0uBMf2A2DZruD+xsgqkjoPpZUFQMh5yEUqcgjDScsr6krI+VQuDDgPQMAwJQlzgFw7pDg13RqVNA6DjQvSFGs4PAIMpBrFKIlnNwpxZECTegWiJYtUB8V4iBdYXI1wEEtCS6giAtIRRqjqvZRYBQ0nijLUqiBgyphIr2AEhLBKQSqlwK6dRUJWrg/GKwAVWCUV0hkFqgSCAX2l94+2tcDIf0Bkk0xjAjAe4KgdQF+zXgSnSH/kRFdFgOXKkIH7QkcnTUAsP4P1QLbe0BlLwkj62FTneFwDUxiCzG1QRTmhhQCoGJg0E9EdsagJREsnJwUNMHSIFP2J/XBOtJF6C6QiTaQzDKWEQUz+q/agrBLvwYBYgmWB+l4Fn7N16UncH/o8ExakLdM++KY1L5JH499K5ctcMn7f9hF/ghg+lKkJapP6XL5ttyJP7oKUX+I/jHPA7+O+N3A4BSvoMtqfiebCAfZD8uMABy+LUBvz6E3wmALvBMRwLNugDfj8MMgEQrrjBfwgtyJVJ6IqJ0oOeaQv3biFokxJF5XbqfjhnYbhQLAMT+ba0JCfxa2ICZFCwBgDcmAJuTYknoSmfeWotaxXKw1DUdqc68jS1q/Si2A4DtxNDQnLYH/0i25LgHQKUxQXj4opmNGXIYy/Rg8GeALvw8HSxmwaIfOYmfXhk+zkWf/RN05PG/BABSi0E15xCsIgOuQxByEMSKUjS6qJnlIgVbsHIsqhALq8X5CgAtFCEIb/8UbGHmfqgWzdgDsKJkXIutdDVmuhxbIR9AahYT0nxSiw8K1BOK9Kga8zYDhJhP3tJEuQDiC3E1sr0HSNdj4+W4ZHUypcjX+H5Kaw+Q6EohbjE+HAwOYNwByIrsnwFwggWhx4Cc5Ut6lD8CQFOX1A9SjlwTZFDSdFiRFOoxE6J0UI6ksRYNl+WMaAQEskChEIvVop0RJAmGXIG+BNtNcEWZCUrySB2ykr0pGGk+1sO4KsQWX5gHvbwbObIJzsEue0FRhE4os6ODMDwqhP9+hgBSna3dof9trI+5wXYPUKQjhTzhQw29SYw9ANWRRbIcqFCKfMFMrODkl1kCDcborZ6Q44JZAoWrYM0Sgc19iTGggYVdGlRTgsyaKFvJZSAYCcA+GN/+UYmQGgu5xmS5pgQzQ0HR8lraA7BbYa8xh7EE0lQmTBrSAWldekKZy6RBE8lQhoXe/IdigIxN8CicAzyS/5NUGlyBJRCrS67kgQsZYDYJjp0DDOwgsBVzUaAvqTHA3QSnOxI00XHOC8KT4IouuXwSKJEmlz0G9k/ANVMKMfqORJ8FD5RCUAUhx5C3FAMw4H8FAF4PBkrTkVoQReXYVBUIXAMWHoSlRdmWnpYvBhMSfK4pwTSrYdmSQMQFlNsDJsrBFPYCddOguBozUQ3JqJTYWlhSmq8HRNmoIptDjtREJpoSzDxssCVFaiFYqYaFa8HfToIVlkUMph48KgnH9TMVaZBNAwi7LkovCIKONiWYEFE5/Vcob2H0IKEkBLmo8x7syyYYkdEkBHDub/W8FLUsiUzbPasKTPwPuaYE8+D1zLwaOa0Io1y2KIk8oAk94Nk5TfDDovxHhNIuZawmuMtHnlXh5zTBj6vAT7vE8Eoh8IDS72t36uNHksjvZGc5RId9EMY2ZlJmBn9kyBvA9nZkVns5XXi0E8D/tEsUr4Ek8qmIZ9d/Aps9JmJ3DpCWQ9Ntuapbwdz+H6sFyhEhWB7AlUSG79HaFMCqBcolArN7f2orDLYohhJh9kkwfiDQlwYUII1ASeKAbHgUIBkxqEYSb94NWFE8pAj3BDGapaCQDK+ngUnje5rgXHturjUvKg3PyaHDQ6HoICw8DG46Dms8AhvsSeg+DfqA5V1XOG18XBNcqYoC0IO66FYVvFMNqtlyoLACJFUP1qIOj+4nWKtB2zXyUuoLUEeOaIITinDwih6Bxv7GeihYDo7oAXJ1oMfqAXM1oaQk0vGCB2vgKPvj7RDUlUSComiwI44jCz1cCMvc06aS0UMc04WnS6ORimigK0RREiFtRCD2x92BKocORdHUfd2MICSUhrEN4QBRUFgM95Qu/HRTgGgPAKrgisZnQj9nf64vglsM13IpNyMJpJSFeSE0J4msy+FrFCQkwgwFVjUohZ+1HUOB9lHAaoJblOJPHQAO5rvbGvE7UdAlfrcpsNqidKnjHz/r5+xvpEFD0PhoIOg4kNaCgzeVu+cAyDwAHoSlKNDa8F+gYHsO0IgfngRwChLrX3MhaAhiwAXwAETRwMyn5AaAlYYDbVEGvANm+yIqtwwGNwDppgDAOUCuI4KQ2wDNUwCqYQbSF8E4CS7KwcHNv8aboBZpuEuDStAdGeyQ+j104URXTK85biUHDBIByMHSSZCBcxH1Bk3c0a6lhHAiCZxqBvH1ZK4n7gFdeJccnMqGD9GJRn8ddioB7HhVpT/woZPgUBma6oqOJ8GVu6ZdZlw2inu/E/2pJVBaDh5Kw6ODsPpN9dnzcM0KwrtOgv37yhOi+JQPIIUgglXEuIUwtwBAFPEnWVBACs3KwaOSOKstCn5TvfBVUCnj10XxfnNcLdfC1mpCNdLGJQ7Cwkr3Gb+wVxeOaYIF8AG2NwZwRVL6pnq8JhTeBhwVxat9EozIoYuieEAQWqkHB6UQGi+BKmpJUOQK5L7r4rdID4CIYEZkdlAMArfEOCSKRzTBeIsF6vyjQyJcV0ffOJjQa8Ep50MHgaPAxcs5QIUIZWxHxn1FFB9zcUwT/A1OBRGSZuggp3k53PSC6grxnToCPMRLFACfoOFJT5jE4v57SMO7mXrmpvgOmo44haEJzi1+aqL4FlH4RXIww2I5Wgn54GAgBdNfyb5AZzoCNIridQfV/O17FkixrdABUXx9B3S5NFwGvgl1QkgMfwVdeLEpgBqmN2IA6QyH7ztqSRBpSoJcQDz45wDsreGtCVBpyoFdRiREewAf+fjWaVDH76/9AGAdhOUSwLgc/kAylEJ+RVkg6vynozsyqAndnoJcrutfCx/uOUBva+hyc1xKF34ZwWAPANu2KM6JQld3cLgiKA3e94IhMoH7AR7vEl9sDu9Y/9q5wSRE8c+2yH8A/EsAPINcvxv4ezk0VQCXK/1antTWerBrefAK+o7eOwh7/J6c58EvS6CHkSshij/FSFQM13tr+uFC4C1uyw3mJg3aeCPY58qhHevvWEC6QjxLQQI2iPZaBwBAEQZKQXBBBNMfIxHv+k7Aysd8+XGKYKJ4loUQthIl8SwLIeyXH4fSmLsoKIji21jANMEVdTSmiFZMHhlqpF6hW7hfn1wCINcRICGMjFSBCSE4xcUfg+BwFWMIEaGNUkkQShVrAY5ZsJdAaeuDDQPsfGj4vcv6LwGg2e9ZqPgSAGFhixb53tQSBKdAq1DrY8DcBQAe/ok7wqXT+njsIxwwM0DifqSTbiCk3a/dADAIvy+2SJFmd0gjf3vSXQLhXSEEU4TDM59E6ykw8KfLweT2APgCGESuwTF4Qh1vRf+K/D0A8NYAidU/2SQLRC7wHtBxhBk1xiomAqRtK1RPB8zd8P8SAFQWqIUIeAyg0j4kEZTZE5k/viFgFxG+5b8CQEdPBrijK0CuK6ZvcV04uPYBUBSFS5s0HEl8C0aEBfvlQeIEYGRDH4iHYvZXSRe47LYorChcyH4QmjQ9OwjM98e34f89AMDz0GKrdDcNWmyJbsF+9f5rHwB4UwDqMJjUwiPnX34KkOIiLIUoXgqhdPpfsi0RVgJW119/HN4eoB1/oRSgkYLXAWCYgOv41TVrtiCmGX9UDJde+vL1YApUgyKD4Fw4WGn4mgr+eMtG/NKAX4D3wzcDFgXXfgZQrDcEVQnJzP0Ofm3FP43WiMV6eF/eyeiiE40htqBX3C/DvwAimNwlcSn8xZYQFv4tBT9LISI9RBE8eQTW4gIg/utdD6An7wlOaYHqergV9G0okN8HYdotA8xqYk7jf6cgapzVCb6AXw7h37VFOfHvc7ponxW5b4L/BfC/UzDO45dvjB/rCuHLZT/6ZcU4+M9eAv1J8GuVglHDL39y/G4APD4gPjDkMwHwz4LfpWA8jl++E363MVbvStDd7VSaISGIred3AZDuB3YGf9fq13ge2fv32l/3ZZDaHRgDwR+1RmR10am+gIpthVjcFhPRHmAUFPEUfoXwsxSE+N09QB28AvtZ7CigywU8/FFz3GIqWKMcFzBCsEnwLW69n/+sAYDg11b8wuFXEv/NE4xhIDwCHCR4ocFrN/6J48e6QnQdhkrQE0X5lhC+91t+P+9pUPyKhG78IB05/NMuApG3ANDoMLwLfMTFUfz6PvTNRQ/wQAmMEKcBbCGM5f1bH5D7OQBVCZcoh6rdDoEUwjjRP8wxIAx9KgZqLTGU58L3BX8YGHYWqFIQiFV/gTWhiZrAG3rHB3YnwUVpPNsbQYOCyVwp5MSIkPtJcEUULzz4gi/gsFfXl1f82E3xuaUvWRRdKYf1je4QIPtqUD0jieBVUIlieMfpd0QgSpCuliCaB5zeDDlEfAWAYpfEFntDkJVQ4nYFwEH7BBhp0ERvBF8UnpJDCSOHWve+Dh2yCQBfC1i8IrTQHI9yfQb5vRpUSTXsAVko3gxCMA58AgQ9Ce4VxKqpB0B6YySQi/d99GmiQaud0YRP0hF+URNJIrv6IZBMVJpBTOD7ewBoyuiV/ihyBHyKEcrm4xTytA9Rxt+MAdgeoLc7Djb5485wm/YcMl6hj+QSqNgUSZPen54ENHCDwbcDAnsEYXPfiTHAMf6dheiaVE3lAoTbAzjrvvQyUG0C3od/dhP8bVhQbBsgAQuj1fI1ChpZCI3/60fspvhzrTKB7Fc6BaI2AWMfAH6DwHryg8yGt2fE5D36pxf9XZaHKag3Q6WM/4uFXSnEgx2RK6cBOA2W98/fmmCNdPEnGfkE5q8HnwD8jbzAPgjT1Gl49iQYPANkj4HFIEDeTgM+Df55Il7mAYWLQM6z8Dj4/Sa4pSsAXwuDVIEIlgq3QN8EHDPeB1bAh9J4uA6E2gatRGxD3+gIkGiMIWRTAKY9CKuLD73gzoVxS+QodAUoVEMWFeHrPlAMDuQtSw7ekVW8Kc3FqrWaUB+55wCe9CHXGCNsClCrhkXmAQf53RHcJVBaE0HWw0tUQY53hr2h153ru4kQUApTuSVRPeS+FESAva+DfDcSqMSNIcIBUEpNAXr1EJbfbx1Bahdlpy+MJQURuUFAdn6vm8UgCDt3YbIwpJDvhBg94kUjIdzIzv2MLl5TvuCEPuIOSAB8v1vDi6eia0icRysfQDuI/+F73AWvT6DdBICODza+aOQgR8b0zgH+JXjRp0a8blJ6eAFmALz6vYD45wtnAeuXfS/GAZZNsNaaRIWfOmXWmeXi9evav0AZnGkigD1QwuyzPh5Gm2DNhj+8AxLD9S0fQPpBbqFfxlLZSAEl9oEChwQzDFjx4Jj7inzg2qdBtSkJgOx7sP4oMwoGYcZDZBNcbwwRZnP42SCMeoncYOytf7sjLJ0AZRsjaDwMhKEvQFtYJ/Sv+0lwvSWEJFvCgsafMAsOZj8NijQFRnQgfGvkrRtY0Cd2N8rNB5aB3wqAEyxExWCr0bcsKMaC5QM7FvwiEAczexyGZf6mEQynWLAvyEg0BpCqLvpmdwf6NP7b8R7mzuOxD4B0P4iaHH7Lgu4eh4RbPjD2058CFRAjZflsR4QQucWCdUGGMfGfKoYjq6KsDcA0nMH5T7Y341jQ5yYAqH4Q40g9WIh8uqZ3fODaR79KrIvP1cOFsDWPvOgI164YTpuKYrOFwDcCNHpglQg618Mt1t+uAnwK6iXgynl/ggIL8w389fuGGD1ZAp9qEOBzkTb7FZVD57TwiA5CiRHQdwBxCRAD/c76TgA4FIAbAIlMr8EG4JWLLQXT9YGtJ8z7PKgu/rQaKGV8xP7T5Xm1f0DB7qLshDBSAHmcEJtAMRxAbOuvuLf+cGNiWQUgFLCKKElmgaZNwbT/nxXn5VGgkfz3vPGP2j+gIAqAugiYkcVujT4jZ3C+74XQ9+5AjaL4AniKAvy/iijQkyp4vhfCfJiCJQC0pguvtUVBmLAgbmPc+pUbAAj4HAVSpUA6KLADADf+OGj8hP3VNbVjf1nuCHO2P8hKUKIpUOnV3w33BPpOWejvffHuAYCv+6kNELwSDilwPAGn4J0L7cZf6w3mLwQt+3/F+hoJsrP/tfQFCnuAtujC4R55NweQiIaViS367a3pQBPYgZ0F9XVHdChwMCMULGOAHjB+Cv827i0KQPtbZv9NwXsApNsBU2kwPvUlQE5xa3fb6V/7hIPIB6AFw88BlEPu+IDv+tY18X88wKGys0E2Hx46/cT+B8gRlgBAHCDXITuVAnh1AHEJ2EL3fcAIAH+k72oSjw0DaeRfsMVA/scYAOJshx0lwrQPuTUYfAUAAjd9L0Lqgoj5/tppPL693+r3vg+8B0AvC7wu/Gb9p1jwEZ6jAKuGEXswyLEwdhdls+1vqVbpzA54GwYO4vUtX0Pe94FdAFBEDAz2IywMF7Osdv/9oxYAaz8FFgug8bcsWMaXl/boWlMAS0kPYC2EdCngUWgm3UAX+6bMlK1HgQh4BtwinDbmyyXCcIOi6pstBIc3g5IiYg19y/jXyw0xddAaiRYEPQ8UYz8Av0fgA1gAVJxBbA0HkwWSXfW2D3gb9xEXLSpH0OwKnYGH5p2A5S2z38fDXQCc1nsCAjH8q3hTPBMAH9UEs18wLyrP/SOH/EY6TF7IAJBv5BVFMjQOAPlwJGgrTW4AyLcwZhdmxQdAOwCkHAnfngndBEARdjjrMXPfzFKj+5lu+9siZnkoTpS0NuoLSwAIWe9X0UUzc6Fm/d4nQDdHV2k6BDa97lsjTsz0082uzx13azC4M2CjFzB5EGQwUDiHsiLfPPkeAAKkNMBuuHwKxM9/THLs2xKg+2ERxMZSkzU9ngJD6mhWj9/VQqWRC9MetiMRNo0Ks+kOgKYjvASAwKca4eOsIh7JgIODgBqm39FzAjyTDc+l/9dToBC8BkvAOgsgcvIQYO7CgPICC/w2AMQtckGelOox6IwIwI/Rd3O+HwDniBDuNMQvhciB12US+ARybTgG7vECNwAaH7QWwsxovzkNAtYHSwA8AF5p850DP59AnrohBon7NOY1AAQubFXyeSFqAsNSSJYDiwnZB0ACHvKygiBIomrIOgtlC4fPCxQDfkHsJEcClIX3ABBb1ZN+EqgKD0XhsugBcMSW66sZAO0sCMcCUg8vHSy8BAAS2S0UCDcG+mIQf/CAWNgFAOUS4Xfh1LBbHOv2J9wEObh1c1l8Dt4xFhxRGJUAVBv2exq4y9rZjgBdLITef2fBCAApEMCGB2Y4xYY/3PpLANSRNAlic1xQgnJgBsyBPNAOoXgKuhVVbgMAR8wy1PrxwUED5+Aw8g+5AWR9b332Pbyg6Ahb5G9P/pAx4una39SyLyvMfNM4ZZzAm9w4sAPgEBF8c9xwD7Q9CkDo2wVAFzyKBdIR/HMAvJvAb0d4DwAcR/1Bdh+MZ0GcJIrsA6D9Qbk3aGITyBOh8twDMhfUkv9y3k3sAMCfLP4JlgGu5MGB9/k0bNCauRSiBXs2w8Pfpw8Dm0u+D4A6//IX72OCDe4rj98nto88bsXzp8T/DcD32hP9E51//Ss0S537EQMR/lj+82+Av4ih8GMRwJ8Xv86//Y1AduJlEQeguxdfVgNW/NvDwLC/fcDUcDpoPuYUOv/+d/Qzpi++zqbBZupXlT88BlKb8R9i53vZ/wkX0PmPf3R+8AMiOdKDG//qW+LPgZkPIfnT4dc5p/z7699f/6pf/w8zV3iwqr7TDAAAAABJRU5ErkJggg==); +} +.toolkit-color-picker > .toolkit-canvas > .toolkit-grayscale { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + + background-repeat: no-repeat; + -webkit-background-size: 100% 100%; + -moz-background-size: 100% 100%; + -ms-background-size: 100% 100%; + -o-background-size: 100% 100%; + background-size: 100% 100%; + background: rgb(0,0,0); + background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeTI9IjEwMCUiPgogICAgPHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzAwMDAwMCIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiNmZmZmZmYiIHN0b3Atb3BhY2l0eT0iMSIvPgogIDwvbGluZWFyR3JhZGllbnQ+CiAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9zdmc+); + background: -moz-linear-gradient(top, rgba(0,0,0,1) 0%, rgba(255,255,255,1) 100%); + background: -webkit-linear-gradient(top, rgba(0,0,0,1) 0%,rgba(255,255,255,1) 100%); + background: linear-gradient(to bottom, rgba(0,0,0,1) 0%,rgba(255,255,255,1) 100%); + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#000000', endColorstr='#ffffff',GradientType=0 ); +} +.toolkit-color-picker > .toolkit-canvas > .toolkit-indicator { + position: absolute; + + width: 40px; + height: 40px; + margin-top: -20px; + margin-left: -20px; + + -webkit-border-radius: 20px; + -moz-border-radius: 20px; + border-radius: 20px; +} + + +.toolkit-color-picker > .toolkit-hex { + grid-area: hex; + text-align: center; +} +.toolkit-color-picker > .toolkit-hex > .toolkit-input { + height: 100%; +} + +.toolkit-color-picker > .toolkit-hue { + grid-area: hue; +} +.toolkit-color-picker >.toolkit-saturation { + grid-area: saturation; +} +.toolkit-color-picker > .toolkit-lightness { + grid-area: lightness; +} + +.toolkit-color-picker > .toolkit-red { + grid-area: red; +} +.toolkit-color-picker > .toolkit-green { + grid-area: green; +} +.toolkit-color-picker > .toolkit-blue { + grid-area: blue; +} + +.toolkit-color-picker > .toolkit-apply { + grid-area: apply; +} +.toolkit-color-picker > .toolkit-cancel { + grid-area: cancel; +} + +/* DIALOG */ + +.toolkit-dialog { + position: fixed; + z-index: 100000000000; + + -webkit-animation: -webkit-dialog-fade-in 0.5s; + -moz-animation: -moz-dialog-fade-in 0.5s; + -ms-animation: -ms-dialog-fade-in 0.5s; + -o-animation: -o-dialog-fade-in 0.5s; + animation: dialog-fade-in 0.5s; + + -webkit-transform: scale(1); + -moz-transform: scale(1); + -ms-transform: scale(1); + -o-transform: scale(1); + transform: scale(1); +} +.toolkit-dialog.toolkit-show { + opacity: 1; +} +.toolkit-dialog.toolkit-hiding { + opacity: 0; + + -webkit-transition: opacity 0.5s, transform 0.5s; + -moz-transition: opacity 0.5s, transform 0.5s; + -ms-transition: opacity 0.5s, transform 0.5s; + -o-transition: opacity 0.5s, transform 0.5s; + transition: opacity 0.5s, transform 0.5s; + + -webkit-transform: scale(0); + -moz-transform: scale(0); + -ms-transform: scale(0); + -o-transform: scale(0); + transform: scale(0); +} +@-webkit-keyframes -webit-dialog-fade-in { + 0% { opacity: 0; -webit-transform: scale(1); } + 100% { opacity: 1; -webit-transform: scale(1);} +} +@-moz-keyframes -moz-dialog-fade-in { + 0% { opacity: 0; -moz-transform: scale(1); } + 100% { opacity: 1; -moz-transform: scale(1);} +} +@-ms-keyframes -ms-dialog-fade-in { + 0% { opacity: 0; -ms-transform: scale(1); } + 100% { opacity: 1; -ms-transform: scale(1);} +} +@-o-keyframes -o-dialog-fade-in { + 0% { opacity: 0; -o-transform: scale(1); } + 100% { opacity: 1; -o-transform: scale(1);} +} +@keyframes dialog-fade-in { + 0% { opacity: 0; transform: scale(1); } + 100% { opacity: 1; transform: scale(1);} +} + +/* EXPANDER */ + +.toolkit-expander { + +} + +.toolkit-expander.toolkit-always-expanded > .toolkit-toggle-expand { + display: none; +} + +/* popup */ +.toolkit-expander.toolkit-popup.toolkit-expanded { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 1000000; + margin: 0; +} +.toolkit-expander.toolkit-popup > .toolkit-toggle-expand { + box-sizing: border-box; + position: absolute; + z-index: 1; + top: 0; + left: 0; + right: 0; + bottom: 0; + width: 100%; + height: 100%; + margin: 0; + opacity: 0; +} +.toolkit-expander.toolkit-popup.toolkit-expanded > .toolkit-toggle-expand { + opacity: 1; + top: 10px; + right: 10px; + left: auto; + bottom: auto; +} + +/* drawer right */ +.toolkit-expander.toolkit-drawer-right { + position: absolute; + top: 0; + right: 0; + transform: translate3d(100%, 0, 0); + transition: transform 500ms; +} + +.toolkit-expander.toolkit-drawer-right.toolkit-expanded { + transform: translate3d(0%, 0, 0); +} + +.toolkit-expander.toolkit-drawer-right > .toolkit-toggle-expand { + position: absolute; + top: 0; + left: 0; + transform: translate3d(-100%, 0, 0); +} + +/* drawer left */ +.toolkit-expander.toolkit-drawer-left { + position: absolute; + top: 0; + left: 0; + transform: translate3d(-100%, 0, 0); + transition: transform 500ms; +} + +.toolkit-expander.toolkit-drawer-left.toolkit-expanded { + transform: translate3d(0%, 0, 0); +} + +.toolkit-expander.toolkit-drawer-left > .toolkit-toggle-expand { + position: absolute; + top: 0; + right: 0; + transform: translate3d(100%, 0, 0); +} + +/* drawer top */ +.toolkit-expander.toolkit-drawer-top { + position: absolute; + top: 0; + left: 0; + transform: translate3d(0, -100%, 0); + transition: transform 500ms; +} + +.toolkit-expander.toolkit-drawer-top.toolkit-expanded { + transform: translate3d(0, 0, 0); +} + +.toolkit-expander.toolkit-drawer-top > .toolkit-toggle-expand { + position: absolute; + bottom: 0; + right: 0; + transform: translate3d(0, 100%, 0); +} + +/* drawer bottom */ +.toolkit-expander.toolkit-drawer-bottom { + position: absolute; + top: 0; + left: 0; + transform: translate3d(0, 100%, 0); + transition: transform 500ms; +} + +.toolkit-expander.toolkit-drawer-bottom.toolkit-expanded { + transform: translate3d(0, 0, 0); +} + +.toolkit-expander.toolkit-drawer-bottom > .toolkit-toggle-expand { + position: absolute; + top: 0; + right: 0; + transform: translate3d(0, -100%, 0); +} + +/* hide */ +.toolkit-expander.toolkit-remove { + display: none; +} + +.toolkit-expander.toolkit-remove.toolkit-expanded { + display: block; +} + +.toolkit-expander.toolkit-remove > .toolkit-toggle-expand { + display: none; +} + +/* FADER */ + +.toolkit-fader { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + box-sizing: border-box; + + display: inline-grid; +} + +.toolkit-fader.toolkit-horizontal { + grid-template-columns: 1fr auto; + grid-template-rows: auto auto auto; +} + +.toolkit-fader.toolkit-vertical { + grid-template-columns: auto auto; + grid-template-rows: auto 1fr auto; +} + +.toolkit-fader.toolkit-top { + grid-template-areas: "track value" + "scale label"; +} +.toolkit-fader.toolkit-left { + grid-template-areas: "label ." + "track scale" + "value ."; +} +.toolkit-fader.toolkit-right { + grid-template-areas: ". label" + "scale track" + ". value"; +} +.toolkit-fader.toolkit-bottom { + grid-template-areas: "scale label" + "track value"; +} + +.toolkit-fader > .toolkit-track { + grid-area: track; + position: relative; +} +.toolkit-fader > .toolkit-scale { + grid-area: scale; +} +.toolkit-fader > .toolkit-label { + grid-area: label; +} +.toolkit-fader.toolkit-vertical > .toolkit-label { + text-align: center; +} +.toolkit-fader.toolkit-horizontal > .toolkit-label { + text-align: left; +} +.toolkit-fader > .toolkit-value { + grid-area: value; +} +.toolkit-fader.toolkit-horizontal > .toolkit-value > .toolkit-input { + text-align: left; +} +.toolkit-fader.toolkit-vertical > .toolkit-value > .toolkit-input { + text-align: center; +} + +.toolkit-fader > .toolkit-track > .toolkit-handle { + position: absolute; + z-index: 10; + will-change: transform; + transform: translate3d(0, 0, 0); +} + +.toolkit-fader.toolkit-horizontal > .toolkit-track > .toolkit-handle { + left: 0; +} + +.toolkit-fader.toolkit-vertical > .toolkit-track > .toolkit-handle { + bottom: 0; +} + +/* GAUGE */ + +.toolkit-gauge { + +} + +.toolkit-gauge > svg { + width: 100%; + height: 100%; +} + +/* ICON */ + +.toolkit-icon { + background-position: center center; + -webkit-background-size: contain; + -moz-background-size: contain; + -ms-background-size: contain; + -o-background-size: contain; + background-size: contain; + background-repeat: no-repeat; +} + +/* KNOB */ + +.toolkit-knob > svg { + width: 100%; + height: 100%; + transform: translate3d(0,0,0); + will-change: transform; + cursor: pointer; +} + +/* LABEL */ + +.toolkit-button.toolkit-select > .toolkit-label { + text-overflow: ellipsis; +} + +/* METERBASE */ + +.toolkit-meter-base { + display: inline-grid; + overflow: hidden; +} +.toolkit-meter-base.toolkit-vertical { + min-height: 128px; + grid-template-columns: auto auto; + grid-template-rows: auto 1fr auto; +} +.toolkit-meter-base.toolkit-horizontal { + min-width: 128px; + grid-template-columns: 1fr 1fr; + grid-template-rows: auto auto auto; +} +.toolkit-meter-base.toolkit-top { + grid-template-areas: + "bar bar" + "scale scale" + "title label"; +} +.toolkit-meter-base.toolkit-bottom { + grid-template-areas: + "title label" + "scale scale" + "bar bar"; +} +.toolkit-meter-base.toolkit-left { + grid-template-areas: + "label ." + "bar scale" + "title ."; +} +.toolkit-meter-base.toolkit-right { + grid-template-areas: + ". label" + "scale bar" + ". title"; +} +.toolkit-meter-base > .toolkit-label { + grid-area: label; + white-space: nowrap; +} +.toolkit-meter-base.toolkit-horizontal > .toolkit-label { + justify-self: end; + align-self: center; +} +.toolkit-meter-base.toolkit-vertical > .toolkit-label { + justify-self: center; + align-self: center; +} +.toolkit-meter-base > .toolkit-title { + grid-area: title; + white-space: nowrap; +} +.toolkit-meter-base.toolkit-horizontal > .toolkit-title { + justify-self: start; + align-self: center; +} +.toolkit-meter-base.toolkit-vertical > .toolkit-title { + justify-self: center; + align-self: center; +} +.toolkit-meter-base > .toolkit-scale { + grid-area: scale; +} +.toolkit-meter-base.toolkit-vertical > .toolkit-scale { + height: 100%; +} +.toolkit-meter-base.toolkit-horizontal > .toolkit-scale { + width: 100%; +} +.toolkit-meter-base.toolkit-top > .toolkit-scale { + justify-self: center; + align-self: start; +} +.toolkit-meter-base.toolkit-bottom > .toolkit-scale { + justify-self: center; + align-self: end; +} +.toolkit-meter-base.toolkit-left > .toolkit-scale { + justify-self: start; + align-self: center; +} +.toolkit-meter-base.toolkit-right > .toolkit-scale { + justify-self: end; + align-self: center; +} +.toolkit-meter-base > .toolkit-bar { + grid-area: bar; + position: relative; + overflow: hidden; + contain: style layout; +} +.toolkit-meter-base.toolkit-vertical > .toolkit-bar { + height: 100%; +} +.toolkit-meter-base.toolkit-horizontal > .toolkit-bar { + width: 100%; +} +.toolkit-meter-base.toolkit-top > .toolkit-bar { + justify-self: center; + align-self: end; +} +.toolkit-meter-base.toolkit-bottom > .toolkit-bar { + justify-self: center; + align-self: start; +} +.toolkit-meter-base.toolkit-left > .toolkit-bar { + justify-self: end; + align-self: center; +} +.toolkit-meter-base.toolkit-right > .toolkit-bar { + justify-self: start; + align-self: center; +} +.toolkit-meter-base > .toolkit-bar > .toolkit-mask { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + will-change: transform; + transform: translate3d(0, 0, 0); +} + +/* LEVELMETER */ +/* out of order because multimeter would override properties */ +.toolkit-level-meter.toolkit-vertical { + grid-template-columns: auto auto; + grid-template-rows: auto auto 1fr auto; +} +.toolkit-level-meter.toolkit-horizontal { + grid-template-columns: 1fr 1fr auto; + grid-template-rows: auto auto auto; +} +.toolkit-level-meter.toolkit-top { + grid-template-areas: + "bar bar clip" + "scale scale ." + "title label label"; +} +.toolkit-level-meter.toolkit-bottom { + grid-template-areas: + "title label label" + "scale scale ." + "bar bar clip"; +} +.toolkit-level-meter.toolkit-left { + grid-template-areas: + "label ." + "clip ." + "bar scale" + "title ."; +} +.toolkit-level-meter.toolkit-right { + grid-template-areas: + ". label" + ". clip" + "scale bar" + ". title"; +} +.toolkit-level-meter > .toolkit-clip { + grid-area: clip; +} +.toolkit-level-meter > .toolkit-bar > .toolkit-peak { + position: absolute; + z-index: 1000; +} + +.toolkit-level-meter.toolkit-vertical.toolkit-left > .toolkit-bar > .toolkit-peak .toolkit-peak-label { + float: left; +} +.toolkit-level-meter.toolkit-vertical.toolkit-right > .toolkit-bar > .toolkit-peak .toolkit-peak-label { + float: right; +} + +.toolkit-level-meter.toolkit-horizontal > .toolkit-bar > .toolkit-peak > .toolkit-peak-label { + transform: rotate(-90deg); +} +.toolkit-level-meter.toolkit-horizontal.toolkit-top > .toolkit-bar > .toolkit-peak > .toolkit-peak-label { + float: right; +} +.toolkit-level-meter.toolkit-horizontal.toolkit-bottom > .toolkit-bar > .toolkit-peak > .toolkit-peak-label { + float: left; +} + +/* MULTIMETER */ + +.toolkit-multi-meter { + display: inline-flex; + flex-wrap: wrap; + justify-content: center; + align-items: stretch; + align-content: center; +} +.toolkit-multi-meter.toolkit-horizontal { + flex-direction: column; +} +.toolkit-multi-meter > .toolkit-title { + height: 1.2em; + line-height: 1.2em; + flex: 1 0 100%; + align-self: start; +} +.toolkit-multi-meter > .toolkit-level-meter { + flex: 0 0 auto; + align-self: stretch; +} +.toolkit-multi-meter.toolkit-horizontal .toolkit-level-meter { + grid-template-columns: 48px 1fr auto 48px; + grid-template-rows: auto auto; +} +.toolkit-multi-meter.toolkit-top .toolkit-level-meter { + grid-template-areas: + "title bar clip label" + ". scale . ."; +} +.toolkit-multi-meter.toolkit-bottom .toolkit-level-meter { + grid-template-areas: + ". scale . ." + "title bar clip label"; +} +.toolkit-multi-meter.toolkit-horizontal .toolkit-level-meter > .toolkit-title { + justify-self: end !important; +} + +/* PAGER */ + +.toolkit-pager { + box-sizing: border-box; + overflow: hidden; + width: 100%; + height: 100%; + display: flex; +} +.toolkit-pager.toolkit-vertical { + flex-direction: column; +} +.toolkit-pager > .toolkit-clip { + flex: 1 1 100%; + position: relative; +} +.toolkit-pager.toolkit-left > .toolkit-clip, +.toolkit-pager.toolkit-top > .toolkit-clip { + order: 2; +} +.toolkit-pager.toolkit-right > .toolkit-clip, +.toolkit-pager.toolkit-bottom > .toolkit-clip { + order: 1; +} +.toolkit-pager > .toolkit-clip > .toolkit-page { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + opacity: 1; + overflow: auto; + will-change: transform; + transform: translate3d(0,0,0); +} +.toolkit-pager > .toolkit-buttonarray { + display: flex; + flex: 0 0 auto; +} +.toolkit-pager.toolkit-left > .toolkit-buttonarray, +.toolkit-pager.toolkit-top > .toolkit-buttonarray { + order: 1; +} +.toolkit-pager.toolkit-right > .toolkit-buttonarray, +.toolkit-pager.toolkit-bottom > .toolkit-buttonarray { + order: 2; +} + + + +.toolkit-pager.toolkit-forward.toolkit-vertical > .toolkit-clip > .toolkit-page.toolkit-hiding { + animation: toolkit-page-vertical-out-forward 0.5s ease-in-out; + transform: translateX(-100%); +} +.toolkit-pager.toolkit-forward.toolkit-vertical > .toolkit-clip > .toolkit-page.toolkit-showing { + animation: toolkit-page-vertical-in-forward 0.5s ease-in-out; +} + +.toolkit-pager.toolkit-backward.toolkit-vertical > .toolkit-clip > .toolkit-page.toolkit-hiding { + animation: toolkit-page-vertical-out-backward 0.5s ease-in-out; + transform: translateX(100%); +} +.toolkit-pager.toolkit-backward.toolkit-vertical > .toolkit-clip > .toolkit-page.toolkit-showing { + animation: toolkit-page-vertical-in-backward 0.5s ease-in-out; +} + +.toolkit-pager.toolkit-forward.toolkit-horizontal > .toolkit-clip > .toolkit-page.toolkit-hiding { + animation: toolkit-page-horizontal-out-forward 0.5s ease-in-out; + transform: translateY(-100%); +} +.toolkit-pager.toolkit-forward.toolkit-horizontal > .toolkit-clip > .toolkit-page.toolkit-showing { + animation: toolkit-page-horizontal-in-forward 0.5s ease-in-out; +} + +.toolkit-pager.toolkit-backward.toolkit-horizontal > .toolkit-clip > .toolkit-page.toolkit-hiding { + animation: toolkit-page-horizontal-out-backward 0.5s ease-in-out; + transform: translateY(100%); +} +.toolkit-pager.toolkit-backward.toolkit-horizontal > .toolkit-clip > .toolkit-page.toolkit-showing { + animation: toolkit-page-horizontal-in-backward 0.5s ease-in-out; +} + +@keyframes toolkit-page-horizontal-in-forward { + 0% { transform: translateY(100%); } + 100% { transform: translateY(0); } +} +@keyframes toolkit-page-horizontal-out-forward { + 0% { transform: translateY(0); } + 100% { transform: translateY(-100%); } +} +@keyframes toolkit-page-horizontal-in-backward { + 0% { transform: translateY(-100%); } + 100% { transform: translateY(0); } +} +@keyframes toolkit-page-horizontal-out-backward { + 0% { transform: translateY(0); } + 100% { transform: translateY(100%); } +} + +@keyframes toolkit-page-vertical-in-forward { + 0% { transform: translateX(100%); } + 100% { transform: translateX(0); } +} +@keyframes toolkit-page-vertical-out-forward { + 0% { transform: translateX(0); } + 100% { transform: translateX(-100%); } +} +@keyframes toolkit-page-vertical-in-backward { + 0% { transform: translateX(-100%); } + 100% { transform: translateX(0); } +} +@keyframes toolkit-page-vertical-out-backward { + 0% { transform: translateX(0); } + 100% { transform: translateX(100%); } +} + + +/* RESPONSEHANDLE */ + +.toolkit-response-handle { + display: block; +} +.toolkit-response-handle.toolkit-inactive { + display: none; +} +.toolkit-response-handle.toolkit-circular > .toolkit-line { + display: none; +} +.toolkit-response-handle.toolkit-circular.toolkit-active > .toolkit-line { + display: block; +} +.toolkit-response-handle.toolkit-line > .toolkit-line-2 { + display: none; +} +.toolkit-response-handle.toolkit-line.toolkit-active > .toolkit-line-2 { + display: block; +} +.toolkit-response-handle.toolkit-block > .toolkit-line-2 { + display: none; +} +.toolkit-response-handle.toolkit-block.toolkit-active > .toolkit-line-2 { + display: block; +} + +/* SCALE */ + +.toolkit-scale { + +} + +.toolkit-scale > .toolkit-label { + position: absolute; + z-index: 4; + display: block; + float: left; +} + +.toolkit-scale > .toolkit-dot { + position: absolute; + z-index: 3; +} + +.toolkit-scale.toolkit-vertical > .toolkit-label { + transform: translateY(50%); +} + +.toolkit-scale.toolkit-vertical > .toolkit-max, +.toolkit-scale.toolkit-vertical.toolkit-reverse > .toolkit-min { + transform: translateY(100%); +} + +.toolkit-scale.toolkit-vertical > .toolkit-min, +.toolkit-scale.toolkit-vertical.toolkit-reverse > .toolkit-max { + transform: translateY(0); +} + +.toolkit-scale.toolkit-horizontal > .toolkit-label { + transform: translateX(-50%); +} + +.toolkit-scale.toolkit-horizontal > .toolkit-max, +.toolkit-scale.toolkit-horizontal.toolkit-reverse > .toolkit-min { + transform: translateX(-100%); +} + +.toolkit-scale.toolkit-horizontal > .toolkit-min, +.toolkit-scale.toolkit-horizontal.toolkit-reverse > .toolkit-max { + transform: translateX(0); +} +.toolkit-scale > .toolkit-pointer { + box-sizing: border-box; + position: absolute; + z-index: 5; + overflow: hidden; + width: 12px; + height: 12px; + border-width: 6px; + border-color: transparent; + border-style: solid; +} +.toolkit-scale.toolkit-vertical > .toolkit-pointer { + margin-bottom: -6px; + bottom: 0px; +} +.toolkit-scale.toolkit-left > .toolkit-pointer { + left: -6px; +} +.toolkit-scale.toolkit-right > .toolkit-pointer { + right: -6px; +} +.toolkit-scale.toolkit-horizontal > .toolkit-pointer { + margin-left: -6px; + left: 0; +} +.toolkit-scale.toolkit-top > .toolkit-pointer { + top: -6px; +} +.toolkit-scale.toolkit-bottom > .toolkit-pointer { + bottom: -6px; +} +.toolkit-scale > .toolkit-bar { + position: absolute; + z-index: 0; +} +.toolkit-scale.toolkit-vertical > .toolkit-bar { + bottom: 0px; +} +.toolkit-scale.toolkit-left > .toolkit-bar { + left: 0px; +} +.toolkit-scale.toolkit-right > .toolkit-bar { + right: 0px; +} +.toolkit-scale.toolkit-horizontal > .toolkit-bar { + left: 0px; +} +.toolkit-scale.toolkit-top > .toolkit-bar { + top: 0px; +} +.toolkit-scale.toolkit-bottom > .toolkit-bar { + bottom: 0px; +} + + +/* SELECT */ + +.toolkit-button.toolkit-select { + flex-direction: row; + justify-content: flex-end; +} +.toolkit-button.toolkit-select > .toolkit-label { + order: 1; + flex: 1 1 auto; +} +.toolkit-button.toolkit-select > .toolkit-icon { + order: 2; + flex: 0 0 auto; + font-size: 16px; + line-height: 16px; + height: 16px; + width: 16px; + margin: 0 0 0 4px !important; +} +.toolkit-select-list { + position: absolute; + z-index: 100000; + box-sizing: border-box; + overflow-y: auto; +} +.toolkit-select-list > .toolkit-option { + white-space: nowrap; + display: block; +} + +/* SLIDER */ + +.toolkit-slider { + background-position: 0 0; + background-repeat: no-repeat; + cursor: pointer; +} + +/* STATE */ + +.toolkit-state > .toolkit-mask { + width: 100%; + height: 100%; +} + +/* TOOLTIP */ + +.toolkit-tooltip { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + pointer-events: none; + z-index: 9999; +} +.toolkit-tooltip > .toolkit-table { + display: table; + width: 100vw; + height: 100vh; + table-layout: auto; +} +.toolkit-tooltip > .toolkit-table > .toolkit-row { + display: table-row; +} +.toolkit-tooltip > .toolkit-table > .toolkit-row > .toolkit-cell { + display: table-cell; + height: auto; +} +.toolkit-tooltip > .toolkit-table > .toolkit-row > .toolkit-cell > .toolkit-entry { + white-space: nowrap; + display: table-cell; +} + +/* VALUE */ + +.toolkit-value > .toolkit-input { + -webkit-user-select: auto !important; + -khtml-user-select: auto !important; + -moz-user-select: auto !important; + -ms-user-select: auto !important; + user-select: auto !important; + text-align: center; + border: none; + outline: none; +} + +/* VALUEBUTTON */ + +.toolkit-valuebutton { + display: inline-grid; + + justify-content: stretch; + align-items: center; + + grid-template-columns: auto 1fr auto; + grid-template-rows: auto auto; + grid-template-areas: "icon label value" + "scale scale value"; +} +.toolkit-valuebutton > .toolkit-icon { + grid-area: icon; +} +.toolkit-valuebutton > .toolkit-label { + grid-area: label; +} +.toolkit-valuebutton > .toolkit-value { + grid-area: value; +} +.toolkit-valuebutton > .toolkit-scale { + grid-area: scale; +} + +/* VALUEKNOB */ + +.toolkit-valueknob { + display: inline-flex; + flex-direction: column; + justify-content: center; + align-items: center; + +} +.toolkit-valueknob > .toolkit-label { + flex: 0 0 auto; +} +.toolkit-valueknob > .toolkit-knob { + width: 100px; + height: 100px; + flex: 0 0 auto; +} +.toolkit-valueknob > .toolkit-value { + flex: 0 0 auto; +} + +/* WINDOW */ + +.toolkit-window { + position: absolute; + z-index: 10000; + overflow: hidden; + display: flex; + flex-direction: column; + justify-content: space-between; +} +.toolkit-window > .toolkit-content { + overflow: auto; + order: 2; + flex: 1 1 auto; +} +.toolkit-window > .toolkit-header { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + cursor: default; + order: 1; + flex: 0 0 auto; + display: flex; + justify-content: space-between; +} +.toolkit-window.toolkit-draggable > .toolkit-header { + cursor: pointer; +} + +.toolkit-window > .toolkit-footer { + cursor: default; + order: 3; + flex: 0 0 auto; + display: flex; + justify-content: space-between; +} +.toolkit-window > .toolkit-header > .toolkit-title, +.toolkit-window > .toolkit-footer > .toolkit-title { + white-space: nowrap; + flex: 1 1 auto; +} +.toolkit-window > .toolkit-header .toolkit-status, +.toolkit-window > .toolkit-footer .toolkit-status { + white-space: nowrap; + flex: 1 1 auto; +} +.toolkit-window > .toolkit-header > .toolkit-icon, +.toolkit-window > .toolkit-footer > .toolkit-icon { + flex: 0 0 auto; +} +.toolkit-window > .toolkit-header > .toolkit-button, +.toolkit-window > .toolkit-footer > .toolkit-button { + flex: 0 0 auto; +} +.toolkit-window.toolkit-shrinked > .toolkit-footer, +.toolkit-window.toolkit-shrinked .toolkit-resize { + display: none !important; +} +.toolkit-window > .toolkit-header > .toolkit-resize, +.toolkit-window > .toolkit-footer > .toolkit-resize { + cursor: pointer; +} +.toolkit-window > .toolkit-header > .toolkit-spacer, +.toolkit-window > .toolkit-footer > .toolkit-spacer { + flex: 1 1 auto; +} diff --git a/share/web_surfaces/builtin/mixer/toolkit/toolkit.js b/share/web_surfaces/builtin/mixer/toolkit/toolkit.js new file mode 100644 index 0000000000..df84bb66f6 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/toolkit.js @@ -0,0 +1,1370 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; + +/** @namespace TK + * + * @description This is the namespace of the Toolkit library. + * It contains all Toolkit classes and constant. + * There are also a couple of utility functions which provide + * compatibility for older browsers. + */ +var TK; + +(function(w) { + +var has_class, add_class, remove_class, toggle_class; +// IE9 +function get_class_name(e) { + if (HTMLElement.prototype.isPrototypeOf(e)) { + return e.className; + } else { + return e.getAttribute("class") || ""; + } +} +function set_class_name(e, s) { + if (HTMLElement.prototype.isPrototypeOf(e)) { + e.className = s; + } else { + e.setAttribute("class", s); + } +} + +if ('classList' in document.createElement("_") && 'classList' in make_svg('text')) { + /** + * Returns true if the node has the given class. + * @param {HTMLElement|SVGElement} node - The DOM node. + * @param {string} name - The class name. + * @returns {boolean} + * @function TK.has_class + */ + has_class = function (e, cls) { return e.classList.contains(cls); } + /** + * Adds a CSS class to a DOM node. + * + * @param {HTMLElement|SVGElement} node - The DOM node. + * @param {...*} names - The class names. + * @function TK.add_class + */ + add_class = function (e) { + var i; + e = e.classList; + for (i = 1; i < arguments.length; i++) { + var a = arguments[i].split(" "); + for (var j = 0; j < a.length; j++) + e.add(a[j]); + } + } + /** + * Removes a CSS class from a DOM node. + * @param {HTMLElement|SVGElement} node - The DOM node. + * @param {...*} names - The class names. + * @function TK.remove_class + */ + remove_class = function (e) { + var i; + e = e.classList; + for (i = 1; i < arguments.length; i++) + e.remove(arguments[i]); + } + /** + * Toggles a CSS class from a DOM node. + * @param {HTMLElement|SVGElement} node - The DOM node. + * @param {string} name - The class name. + * @function TK.toggle_class + */ + toggle_class = function (e, cls, cond) { + /* The second argument to toggle is not implemented in IE, + * so we never use it */ + if (arguments.length >= 3) { + if (cond) { + add_class(e, cls); + } else { + remove_class(e, cls); + } + } else e.classList.toggle(cls); + }; +} else { + has_class = function (e, cls) { + return get_class_name(e).split(" ").indexOf(cls) !== -1; + }; + add_class = function (e) { + var i, cls; + var a = get_class_name(e).split(" "); + + for (i = 1; i < arguments.length; i++) { + cls = arguments[i]; + if (a.indexOf(cls) === -1) { + a.push(cls); + } + } + set_class_name(e, a.join(" ")); + }; + remove_class = function(e) { + var j, cls, i; + var a = get_class_name(e).split(" "); + + for (j = 1; j < arguments.length; j++) { + cls = arguments[j]; + i = a.indexOf(cls); + + if (i !== -1) { + do { + a.splice(i, 1); + i = a.indexOf(cls); + } while (i !== -1); + + } + } + + set_class_name(e, a.join(" ")); + }; + toggle_class = function(e, cls, cond) { + if (arguments.length < 3) { + cond = !has_class(e, cls); + } + if (cond) { + add_class(e, cls); + } else { + remove_class(e, cls); + } + }; +} + +var data_store; +var data; + +if ('WeakMap' in window) { + data = function(e) { + var r; + if (!data_store) data_store = new window.WeakMap(); + + r = data_store[e]; + + if (!r) { + data_store[e] = r = {}; + } + + return r; + }; +} else { + data_store = []; + var data_keys = []; + data = function(e) { + if (typeof(e) !== "object") throw("Cannot store data for non-objects."); + var k = data_keys.indexOf(e); + var r; + if (k === -1) { + data_keys.push(e); + k = data_store.push({}) - 1; + } + return data_store[k]; + }; +} + +var get_style; + +if ('getComputedStyle' in window) { + /** + * Returns the computed style of a node. + * + * @param {HTMLElement|SVGElement} node - The DOM node. + * @param {string} property - The CSS property name. + * @returns {string} + * + * @function TK.get_style + */ + get_style = function(e, style) { + return window.getComputedStyle(e).getPropertyValue(style); + }; +} else { + get_style = function(e, style) { + return e.currentStyle[style]; + }; +} + +var class_regex = /[^A-Za-z0-9_\-]/; +function is_class_name (str) { + /** + * Returns true ii a string could be a class name. + * @param {string} string - The string to test + * @function TK.is_class_name + * @returns {boolean} + */ + return !class_regex.test(str); +} + +function get_max_time(string) { + /** + * Returns the maximum value (float) of a comma separated string. It is used + * to find the longest CSS animation in a set of multiple animations. + * @param {string} string - The comma separated string. + * @function TK.get_max_time + * @returns {number} + * @example + * get_max_time(get_style(DOMNode, "animation-duration")); + */ + var ret = 0, i, tmp, s = string; + + if (typeof(s) === "string") { + s = s.split(","); + for (i = 0; i < s.length; i++) { + tmp = parseFloat(s[i]); + + if (tmp > 0) { + if (-1 === s[i].search("ms")) tmp *= 1000; + if (tmp > ret) ret = tmp; + } + } + } + + return ret|0; +} + +function get_duration(element) { + /** + * Returns the longest animation duration of CSS animations and transitions. + * @param {HTMLElement} element - The element to evalute the animation duration for. + * @function TK.get_duration + * @returns {number} + */ + return Math.max(get_max_time(get_style(element, "animation-duration")) + + get_max_time(get_style(element, "animation-delay")), + get_max_time(get_style(element, "transition-duration")) + + get_max_time(get_style(element, "transition-delay"))); +} + +function get_id(id) { + /** + * Returns the DOM node with the given ID. Shorthand for document.getElementById. + * @param {string} id - The ID to search for + * @function TK.get_id + * @returns {HTMLElement} + */ + return document.getElementById(id); +} +function get_class(cls, element) { + /** + * Returns all elements as NodeList of a given class name. Optionally limit the list + * to all children of a specific DOM node. Shorthand for element.getElementsByClassName. + * @param {string} class - The name of the class + * @param {DOMNode} element - Limit search to child nodes of this element. Optional. + * @returns {NodeList} + * @function TK.get_class + */ + return (element ? element : document).getElementsByClassName(cls); +} +function get_tag(tag, element) { + /** + * Returns all elements as NodeList of a given tag name. Optionally limit the list + * to all children of a specific DOM node. Shorthand for element.getElementsByTagName. + * @param {string} tag - The name of the tag + * @param {DOMNode} element - Limit search to child nodes of this element. Optional. + * @returns {NodeList} + * @function TK.get_tag + */ + return (element ? element : document).getElementsByTagName(tag); +} +function element(tag) { + /** + * Returns a newly created HTMLElement. + * @param {string} tag - The type of the element + * @param {...object} attributes - Optional mapping of attributes for the new node + * @param {...string} class - Optional class name for the new node + * @returns HTMLElement + * @function TK.element + */ + var n = document.createElement(tag); + var i, v, j; + for (i = 1; i < arguments.length; i++) { + v = arguments[i]; + if (typeof v === "object") { + for (var key in v) { + if (v.hasOwnProperty(key)) + n.setAttribute(key, v[key]); + } + } else if (typeof v === "string") { + add_class(n, v); + } else throw("unsupported argument to TK.element"); + } + return n; +} +function empty(element) { + /** + * Removes all child nodes from an HTMLElement. + * @param {HTMLElement} element - The element to clean up + * @function TK.empty + */ + while (element.lastChild) element.removeChild(element.lastChild); +} +function set_text(element, text) { + /** + * Sets a string as new exclusive text node of an HTMLElement. + * @param {HTMLElement} element - The element to clean up + * @param {string} text - The string to set as text content + * @function TK.set_text + */ + if (element.childNodes.length === 1 && typeof element.childNodes[0].data === "string") + element.childNodes[0].data = text; + else + element.textContent = text; +} +function html(string) { + /** + * Returns a documentFragment containing the result of a string parsed as HTML. + * @param {string} html - A string to parse as HTML + * @returns {HTMLFragment} + * @function TK.html + */ + /* NOTE: setting innerHTML on a document fragment is not supported */ + var e = document.createElement("div"); + var f = document.createDocumentFragment(); + e.innerHTML = string; + while (e.firstChild) f.appendChild(e.firstChild); + return f; +} +function set_content(element, content) { + /** + * Sets the (exclusive) content of an HTMLElement. + * @param {HTMLElement} element - The element receiving the content + * @param {String|HTMLElement} content - A string or HTMLElement to set as content + * @function TK.set_content + */ + if (is_dom_node(content)) { + empty(element); + if (content.parentNode) { + TK.warn("set_content: possible reuse of a DOM node. cloning\n"); + content = content.cloneNode(true); + } + element.appendChild(content); + } else { + set_text(element, content + ""); + } +} +function insert_after(newnode, refnode) { + /** + * Inserts one HTMLELement after another in the DOM tree. + * @param {HTMLElement} newnode - The new node to insert into the DOM tree + * @param {HTMLElement} refnode - The reference element to add the new element after + * @function TK.insert_after + */ + if (refnode.parentNode) + refnode.parentNode.insertBefore(newnode, refnode.nextSibling); +} +function insert_before(newnode, refnode) { + /** + * Inserts one HTMLELement before another in the DOM tree. + * @param {HTMLElement} newnode - The new node to insert into the DOM tree + * @param {HTMLElement} refnode - The reference element to add the new element before + * @function TK.insert_before + */ + if (refnode.parentNode) + refnode.parentNode.insertBefore(newnode, refnode); +} +function width() { + /** + * Returns the width of the viewport. + * @returns {number} + * @function TK.width + */ + return Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0, document.body.clientWidth || 0); +} +function height() { + /** + * Returns the height of the viewport. + * @returns {number} + * @function TK.height + */ + return Math.max(document.documentElement.clientHeight, window.innerHeight || 0, document.body.clientHeight || 0); +} +function scroll_top(element) { + /** + * Returns the amount of CSS pixels the document or an optional element is scrolled from top. + * @param {HTMLElement} element - The element to evaluate. Optional. + * @returns {number} + * @functionTK.scroll_top + */ + if (element) + return element.scrollTop; + return Math.max(document.documentElement.scrollTop || 0, window.pageYOffset || 0, document.body.scrollTop || 0); +} +function scroll_left(element) { + /** + * Returns the amount of CSS pixels the document or an optional element is scrolled from left. + * @param {HTMLElement} element - The element to evaluate. Optional. + * @returns {number} + * @functionTK.scroll_left + */ + if (element) + return element.scrollLeft; + return Math.max(document.documentElement.scrollLeft, window.pageXOffset || 0, document.body.scrollLeft || 0); +} +function scroll_all_top(element) { + /** + * Returns the sum of CSS pixels an element and all of its parents are scrolled from top. + * @param {HTMLElement} element - The element to evaluate + * @returns {number} + * @functionTK.scroll_all_top + */ + var v = 0; + while (element = element.parentNode) v += element.scrollTop || 0; + return v; +} +function scroll_all_left(element) { + /** + * Returns the sum of CSS pixels an element and all of its parents are scrolled from left. + * @param {HTMLElement} element - The element to evaluate + * @returns {number} + * @functionTK.scroll_all_left + */ + var v = 0; + while (element = element.parentNode) v += element.scrollLeft || 0; + return v; +} +function position_top(e, rel) { + /** + * Returns the position from top of an element in relation to the document + * or an optional HTMLElement. Scrolling of the parent is taken into account. + * @param {HTMLElement} element - The element to evaluate + * @param {HTMLElement} relation - The element to use as reference. Optional. + * @returns {number} + * @function TK.position_top + */ + var top = parseInt(e.getBoundingClientRect().top); + var f = fixed(e) ? 0 : scroll_top(); + return top + f - (rel ? position_top(rel) : 0); +} +function position_left(e, rel) { + /** + * Returns the position from the left of an element in relation to the document + * or an optional HTMLElement. Scrolling of the parent is taken into account. + * @param {HTMLElement} element - The element to evaluate + * @param {HTMLElement} relation - The element to use as reference. Optional. + * @returns {number} + * @function TK.position_left + */ + var left = parseInt(e.getBoundingClientRect().left); + var f = fixed(e) ? 0 : scroll_left(); + return left + f - (rel ? position_left(rel) : 0); +} +function fixed(e) { + /** + * Returns if an element is positioned fixed to the viewport + * @param {HTMLElement} element - the element to evaluate + * @returns {boolean} + * @function TK.fixed + */ + return getComputedStyle(e).getPropertyValue("position") === "fixed"; +} +function outer_width(element, margin, width) { + /** + * Gets or sets the outer width of an element as CSS pixels. The box sizing + * method is taken into account. + * @param {HTMLElement} element - the element to evaluate / manipulate + * @param {boolean} margin - Determine if margin is included + * @param {number} width - If defined the elements outer width is set to this value + * @returns {number} + * @functionTK.outer_width + */ + var m = 0; + if (margin) { + var cs = getComputedStyle(element); + m += parseFloat(cs.getPropertyValue("margin-left")); + m += parseFloat(cs.getPropertyValue("margin-right")); + } + if (width !== void(0)) { + if (box_sizing(element) === "content-box") { + var css = css_space(element, "padding", "border"); + width -= css.left + css.right; + } + width -= m; + // TODO: fixme + if (width < 0) return 0; + element.style.width = width + "px"; + return width; + } else { + var w = element.getBoundingClientRect().width; + return w + m; + } +} +function outer_height(element, margin, height) { + /** + * Gets or sets the outer height of an element as CSS pixels. The box sizing + * method is taken into account. + * @param {HTMLElement} element - the element to evaluate / manipulate + * @param {boolean} margin - Determine if margin is included + * @param {number} height - If defined the elements outer height is set to this value + * @returns {number} + * @functionTK.outer_height + */ + var m = 0; + if (margin) { + var cs = getComputedStyle(element, null); + m += parseFloat(cs.getPropertyValue("margin-top")); + m += parseFloat(cs.getPropertyValue("margin-bottom")); + } + if (height !== void(0)) { + if (box_sizing(element) === "content-box") { + var css = css_space(element, "padding", "border"); + height -= css.top + css.bottom; + } + height -= m; + // TODO: fixme + if (height < 0) return 0; + element.style.height = height + "px"; + return height; + } else { + var h = element.getBoundingClientRect().height; + return h + m; + } +} +function inner_width(element, width) { + /** + * Gets or sets the inner width of an element as CSS pixels. The box sizing + * method is taken into account. + * @param {HTMLElement} element - the element to evaluate / manipulate + * @param {number} width - If defined the elements inner width is set to this value + * @returns {number} + * @functionTK.inner_width + */ + var css = css_space(element, "padding", "border"); + var x = css.left + css.right; + if (width !== void(0)) { + if (box_sizing(element) === "border-box") + width += x; + // TODO: fixme + if (width < 0) return 0; + element.style.width = width + "px"; + return width; + } else { + var w = element.getBoundingClientRect().width; + return w - x; + } +} +function inner_height(element, height) { + /** + * Gets or sets the inner height of an element as CSS pixels. The box sizing + * method is taken into account. + * @param {HTMLElement} element - the element to evaluate / manipulate + * @param {number} height - If defined the elements outer height is set to this value + * @returns {number} + * @functionTK.inner_height + */ + var css = css_space(element, "padding", "border"); + var y = css.top + css.bottom; + if (height !== void(0)) { + if (box_sizing(element) === "border-box") + height += y; + // TODO: fixme + if (height < 0) return 0; + element.style.height = height + "px"; + return height; + } else { + var h = element.getBoundingClientRect().height; + return h - y; + } +} +function box_sizing(element) { + /** + * Returns the box-sizing method of an HTMLElement. + * @param {HTMLElement} element - The element to evaluate + * @returns {string} + * @functionTK.box_sizing + */ + var cs = getComputedStyle(element, null); + if (cs.getPropertyValue("box-sizing")) return cs.getPropertyValue("box-sizing"); + if (cs.getPropertyValue("-moz-box-sizing")) return cs.getPropertyValue("-moz-box-sizing"); + if (cs.getPropertyValue("-webkit-box-sizing")) return cs.getPropertyValue("-webkit-box-sizing"); + if (cs.getPropertyValue("-ms-box-sizing")) return cs.getPropertyValue("-ms-box-sizing"); + if (cs.getPropertyValue("-khtml-box-sizing")) return cs.getPropertyValue("-khtml-box-sizing"); +} +function css_space(element) { + /** + * Returns the overall spacing around an HTMLElement of all given attributes. + * @param {HTMLElement} element - The element to evaluate + * @param{...string} The CSS attributes to take into account + * @returns {object} An object with the members "top", "bottom", "lfet", "right" + * @function TK.css_space + * @example + * TK.css_space(element, "padding", "border"); + */ + var cs = getComputedStyle(element, null); + var o = {top: 0, right: 0, bottom: 0, left: 0}; + var a; + var s; + for (var i = 1; i < arguments.length; i++) { + a = arguments[i]; + for (var p in o) { + if (o.hasOwnProperty(p)) { + s = a + "-" + p; + if (a === "border") s += "-width"; + } + o[p] += parseFloat(cs.getPropertyValue(s)); + } + } + return o; +} + +var number_attributes = [ + "animation-iteration-count", + "column-count", + "flex-grow", + "flex-shrink", + "opacity", + "order", + "z-index" +] + +function set_styles(elem, styles) { + /** + * Set multiple CSS styles onto an HTMLElement. + * @param {HTMLElement} element - the element to add the styles to + * @param {object} styles - A mapping containing all styles to add + * @function TK.set_styles + * @example + * TK.set_styles(element, {"width":"100px", "height":"100px"}); + */ + var key, v; + var s = elem.style; + for (key in styles) if (styles.hasOwnProperty(key)) { + v = styles[key]; + if (typeof v !== "number" && !v) { + delete s[key]; + } else { + if (typeof v === "number" && number_attributes.indexOf(key) == -1) { + TK.warn("TK.set_styles: use of implicit px conversion is _deprecated_ and will be removed in the future."); + v = v.toFixed(3) + "px"; + } + s[key] = v; + } + } +} +function set_style(e, style, value) { + /** + * Sets a single CSS style onto an HTMLElement. It is used to autimatically + * add "px" to numbers and trim them to 3 digits at max. DEPRECATED! + * @param {HTMLElement} element - The element to set the style to + * @param {string} style - The CSS attribute to set + * @param {string|number} value - The value to set the CSS attribute to + * @function TK.set_style + */ + if (typeof value === "number") { + /* By default, numbers are transformed to px. I believe this is a very _dangerous_ default + * behavior, because it breaks other number like properties _without_ warning. + * this is now deprecated. */ + TK.warn("TK.set_style: use of implicit px conversion is _deprecated_ and will be removed in the future."); + value = value.toFixed(3) + "px"; + } + e.style[style] = value; +} +var _id_cnt = 0; +function unique_id() { + /** + * Generate a unique ID string. + * @returns {string} + * @function TK.unique_id + */ + var id; + do { id = "tk-" + _id_cnt++; } while (document.getElementById(id)); + return id; +}; + +/** + * Generates formatting functions from sprintf-style format strings. + * This is generally faster when the same format string is used many times. + * + * @returns {function} A formatting function. + * @param {string} fmt - The format string. + * @function TK.FORMAT + * @example + * var f = TK.FORMAT("%.2f Hz"); + * @see TK.sprintf + */ +function FORMAT(fmt) { + var args = []; + var s = "return "; + var res; + var last = 0; + var argnum = 0; + var precision; + var regexp = /%(\.\d+)?([bcdefgosO%])/g; + var argname; + + while (res = regexp.exec(fmt)) { + if (argnum) s += "+"; + s += JSON.stringify(fmt.substr(last, regexp.lastIndex - res[0].length - last)); + s += "+"; + argname = "a"+argnum; + if (args.indexOf(argname) === -1) + args.push(argname); + if (argnum+1 < arguments.length) { + argname = "(" + sprintf(arguments[argnum+1].replace("%", "%s"), argname) + ")"; + } + switch (res[2].charCodeAt(0)) { + case 100: // d + s += "("+argname+" | 0)"; + break; + case 102: // f + if (res[1]) { // length qualifier + precision = parseInt(res[1].substr(1)); + s += "(+"+argname+").toFixed("+precision+")"; + } else { + s += "(+"+argname+")"; + } + break; + case 115: // s + s += argname; + break; + case 37: + s += "\"%\""; + argnum--; + break; + case 79: + case 111: + s += "JSON.stringify("+argname+")"; + break; + default: + throw("unknown format:"+res[0]); + break; + } + argnum++; + last = regexp.lastIndex; + } + + if (argnum) s += "+"; + s += JSON.stringify(fmt.substr(last)); + + return new Function(args, s); +} + +/** + * Formats the arguments according to a given format string. + * @returns {function} A formatting function. + * @param {string} fmt - The format string. + * @param {...*} args - The format arguments. + * @function TK.sprintf + * @example + * TK.sprintf("%d Hz", 440); + * @see TK.FORMAT + */ +function sprintf(fmt) { + var arg_len = arguments.length; + var i, last_fmt; + var c, arg_num = 1; + var ret = []; + var precision, s; + var has_precision = false; + + for (last_fmt = 0; -1 !== (i = fmt.indexOf("%", last_fmt)); last_fmt = i+1) { + if (last_fmt < i) { + ret.push(fmt.substring(last_fmt, i)); + } + + i ++; + + if (has_precision = (fmt.charCodeAt(i) === 46 /* '.' */)) { + i++; + precision = parseInt(fmt.substr(i)); + while ((c = fmt.charCodeAt(i)) >= 48 && c <= 57) i++; + } + + c = fmt.charCodeAt(i); + + if (c === 37) { + ret.push("%"); + continue; + } + + s = arguments[arg_num++]; + + switch (fmt.charCodeAt(i)) { + case 102: /* f */ + s = +s; + if (has_precision) { + s = s.toFixed(precision); + } + break; + case 100: /* d */ + s = s|0; + break; + case 115: /* s */ + break; + case 79: /* O */ + case 111: /* o */ + s = JSON.stringify(s); + break; + default: + throw("Unsupported format."); + } + + ret.push(s); + + last_fmt = i+1; + } + + if (last_fmt < fmt.length) { + ret.push(fmt.substring(last_fmt, fmt.length)); + } + + return ret.join(""); +} + +function escapeHTML(text) { + /** + * Escape an HTML string to be displayed as text. + * @param {string} html - The HTML code to escape + * @returns {string} + * @function TK.escapeHTML + */ + var map = { + '&' : '&', + '<' : '<', + '>' : '>', + '"' : '"', + "'" : ''' + }; + return text.replace(/[&<>"']/g, function(m) { return map[m]; }); +} + +function is_touch() { + /** + * Check if a device is touch-enabled. + * @returns {boolean} + * @function TK.is_touch + */ + return 'ontouchstart' in window // works on most browsers + || 'onmsgesturechange' in window; // works on ie10 +} +function os() { + /** + * Return the operating system + * @returns {string} + * @function TK.os + */ + var ua = navigator.userAgent.toLowerCase(); + if (ua.indexOf("android") > -1) + return "Android"; + if (/iPad/i.test(ua) || /iPhone OS 3_1_2/i.test(ua) || /iPhone OS 3_2_2/i.test(ua)) + return "iOS"; + if ((ua.match(/iPhone/i)) || (ua.match(/iPod/i))) + return "iOS"; + if (navigator.appVersion.indexOf("Win")!=-1) + return "Windows"; + if (navigator.appVersion.indexOf("Mac")!=-1) + return "MacOS"; + if (navigator.appVersion.indexOf("X11")!=-1) + return "UNIX"; + if (navigator.appVersion.indexOf("Linux")!=-1) + return "Linux"; +} +function make_svg(tag, args) { + /** + * Creates and returns an SVG child element. + * @param {string} tag - The element to create as string, e.g. "line" or "g" + * @param {object} arguments - The attributes to set onto the element + * @returns {SVGElement} + */ + var el = document.createElementNS('http://www.w3.org/2000/svg', "svg:" + tag); + for (var k in args) + el.setAttribute(k, args[k]); + return el; +} +function seat_all_svg(parent) { + /** + * Searches for all SVG that don't have the class "svg-fixed" and re-positions them + * in order to avoid blurry lines. + * @param {HTMLElement} parent - If set only children of parent are searched + * @function TK.seat_all_svg + */ + var a = get_tag("svg", parent); + for (var i = 0; i < a.length; i++) { + if (!has_class(a[i], "svg-fixed")) + seat_svg(a[i]); + } +} +function seat_svg(e) { + /** + * Move SVG for some sub-pixel if their position in viewport is not int. + * @param {SVGElement} svg - The SVG to manipulate + * @function TK.seat_svg + */ + if (retrieve(e, "margin-left") === null) { + store(e, "margin-left", parseFloat(get_style(e, "margin-left"))); + } else { + e.style.marginLeft = retrieve(e, "margin-left") || 0; + } + var l = parseFloat(retrieve(e, "margin-left")) || 0; + var b = e.getBoundingClientRect(); + var x = b.left % 1; + if (x) { + if (x < 0.5) l -= x; + else l += (1 - x); + } + if (e.parentElement && get_style(e.parentElement, "text-align") === "center") + l += 0.5; + e.style.marginLeft = l + "px"; + //console.log(l); + if (retrieve(e, "margin-top") === null) { + store(e, "margin-top", parseFloat(get_style(e, "margin-top"))); + } else { + e.style.marginTop = retrieve(e, "margin-top") || 0; + } + var t = parseFloat(retrieve(e, "margin-top") || 0); + var b = e.getBoundingClientRect(); + var y = b.top % 1; + if (y) { + if (x < 0.5) t -= y; + else t += (1 - y); + } + //console.log(t); + e.style.marginTop = t + "px"; +} +function delayed_callback(timeout, cb, once) { + var tid; + var args; + + var my_cb = function() { + tid = null; + cb.apply(this, args); + }; + return function() { + args = Array.prototype.slice.call(arguments); + + if (tid) + window.clearTimeout(tid); + else if (once) once(); + tid = window.setTimeout(my_cb, timeout); + }; +} + +function store(e, key, val) { + /** + * Store a piece of data in an object. + * @param {object} object - The object to store the data + * @param {string} key - The key to identify the memory + * @param {*} data - The data to store + * @function TK.store + */ + data(e)[key] = val; +} +function retrieve(e, key) { + /** + * Retrieve a piece of data from an object. + * @param {object} object - The object to retrieve the data from + * @param {string} key - The key to identify the memory + * @function TK.retrieve + * @returns {*} + */ + return data(e)[key]; +} +function merge(dst) { + /** + * Merge two or more objects. The second and all following objects + * will be merged into the first one. + * @param {...object} object - The objects to merge + * @returns {object} + * @function TK.merge + */ + //console.log("merging", src, "into", dst); + var key, i, src; + for (i = 1; i < arguments.length; i++) { + src = arguments[i]; + for (key in src) { + dst[key] = src[key]; + } + } + return dst; +} +function object_and(orig, filter) { + /** + * Filter an object via white list. + * @param {object} origin - The object to filter + * @param {object} filter - The object containing the white list + * @returns {object} The filtered result + * @function TK.object_and + */ + var ret = {}; + for (var key in orig) { + if (filter[key]) ret[key] = orig[key]; + } + return ret; +} +function object_sub(orig, filter) { + /** + * Filter an object via black list. + * @param {object} origin - The object to filter + * @param {object} filter - The object containing the black list + * @returns {object} The filtered result + * @function TK.object_sub + */ + var ret = {}; + for (var key in orig) { + if (!filter[key]) ret[key] = orig[key]; + } + return ret; +} +function to_array(collection) { + /** + * Convert any collection (like NodeList) into an array. + * @param {collection} collection - The collection to convert into an array + * @returns {array} + * @functionTK.to_array + */ + var ret = new Array(collection.length); + var i; + + for (i = 0; i < ret.length; i++) { + ret[i] = collection[i]; + } + + return ret; +} +function is_dom_node(o) { + /* this is broken for SVG */ + return typeof o === "object" && o instanceof Node; +} + +// NOTE: IE9 will throw errors when console is used without debugging tools. In general, it +// is better for log/warn to silently fail in case of error. This unfortunately means that +// warnings might be lost, but probably better than having diagnostics and debugging code +// break an application + +/** + * Generates an error to the JavaScript console. This is virtually identical to console.error, however + * it can safely be used in browsers which do not support it. + * + * @param {...*} args + * @function TK.error + */ +function error() { + if (!window.console) return; + try { + window.console.error.apply(window.console, arguments); + } catch(e) {} +} + +/** + * Generates a warning to the JavaScript console. This is virtually identical to console.warn, however + * it can safely be used in browsers which do not support it. + * + * @param {...*} args + * @function TK.warn + */ +function warn() { + if (!window.console) return; + try { + window.console.warn.apply(window.console, arguments); + } catch(e) {} +} +/** + * Generates a log message to the JavaScript console. This is virtually identical to console.log, however + * it can safely be used in browsers which do not support it. + * + * @param {...*} args + * @function TK.log + */ +function log() { + if (!window.console) return; + try { + window.console.log.apply(window.console, arguments); + } catch(e) {} +} + +function print_widget_tree(w, depth) { + if (!depth) depth = 0; + + var print = function(fmt) { + var extra = Array.prototype.slice.call(arguments, 1); + if (depth) fmt = nchars(depth, " ") + fmt; + var args = [ fmt ]; + log.apply(TK, args.concat(extra)); + }; + + var nchars = function(n, c) { + var ret = new Array(n); + + for (var i = 0; i < n; i++) ret[i] = c; + + return ret.join(""); + }; + + var C = w.children; + var nchildren = C ? C.length : 0; + + var state = [ ]; + + state.push(w._drawn ? "show" : "hide"); + + if (w.needs_redraw) state.push("redraw"); + if (w.needs_resize) state.push("resize"); + + + print("%s (%s, children: %o)", w._class, state.join(" "), nchildren); + + if (C) { + for (var i = 0; i < C.length; i++) print_widget_tree(C[i], depth+1); + } +} + +/* Detection and handling for passive event handler support. + * The chrome team has threatened to make passive event handlers + * the default in a future version. To make sure that this does + * not break our code, we explicitly register 'active' event handlers + * for most cases. + */ + +/* generic code, supports node arrays */ +function add_event_listener(e, type, cb, options) { + if (Array.isArray(e)) { + for (var i = 0; i < e.length; i++) + e[i].addEventListener(type, cb, options); + } else e.addEventListener(type, cb, options); +} +function remove_event_listener(e, type, cb, options) { + if (Array.isArray(e)) { + for (var i = 0; i < e.length; i++) + e[i].removeEventListener(type, cb, options); + } else e.removeEventListener(type, cb, options); +} + +/* Detect if the 'passive' option is supported. + * This code has been borrowed from mdn */ +var passiveSupported = false; + +try { + var options = Object.defineProperty({}, "passive", { + get: function() { + passiveSupported = true; + } + }); + + window.addEventListener("test", null, options); + window.removeEventListener("test", null); +} catch(err) {} + +var active_options, passive_options; + +if (passiveSupported) { + active_options = { passive: false }; + passive_options = { passive: true }; +} else { + active_options = false; + passive_options = false; +} + +function add_active_event_listener(e, type, cb) { + add_event_listener(e, type, cb, active_options); +} +function remove_active_event_listener(e, type, cb) { + remove_event_listener(e, type, cb, active_options); +} +function add_passive_event_listener(e, type, cb) { + add_event_listener(e, type, cb, passive_options); +} +function remove_passive_event_listener(e, type, cb) { + remove_event_listener(e, type, cb, passive_options); +} + +TK = w.toolkit = w.TK = { + // ELEMENTS + S: new w.DOMScheduler(), + is_dom_node: is_dom_node, + get_id: get_id, + get_class: get_class, + get_tag: get_tag, + element : element, + empty: empty, + set_text : set_text, + set_content : set_content, + has_class : has_class, + remove_class : remove_class, + add_class : add_class, + toggle_class : toggle_class, + is_class_name : is_class_name, + + insert_after: insert_after, + insert_before: insert_before, + + // WINDOW + + width: width, + height: height, + + // DIMENSIONS + + scroll_top: scroll_top, + scroll_left: scroll_left, + scroll_all_top: scroll_all_top, + scroll_all_left: scroll_all_left, + + position_top: position_top, + position_left: position_left, + + fixed: fixed, + + outer_width : outer_width, + + outer_height : outer_height, + + inner_width: inner_width, + + inner_height: inner_height, + + box_sizing: box_sizing, + + css_space: css_space, + + // CSS AND CLASSES + + set_styles : set_styles, + set_style: set_style, + get_style: get_style, + get_duration: get_duration, + + // STRINGS + + unique_id: unique_id, + + FORMAT : FORMAT, + + sprintf : sprintf, + html : html, + + escapeHTML : escapeHTML, + + // OS AND BROWSER CAPABILITIES + + is_touch: is_touch, + os: os, + + browser: function () { + /** + * Returns the name of the browser + * @returns {string} + * @function TK.browser + */ + var ua = navigator.userAgent, tem, M = ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || []; + if (/trident/i.test(M[1])) { + tem = /\brv[ :]+(\d+)/g.exec(ua) || []; + return { name : 'IE', version : (tem[1]||'') }; + } + if (M[1] === 'Chrome') { + tem = ua.match(/\bOPR\/(\d+)/) + if (tem!=null) + return { name : 'Opera', version : tem[1] }; + } + M = M[2] ? [M[1], M[2]] : [navigator.appName, navigator.appVersion, '-?']; + if ((tem = ua.match(/version\/(\d+)/i)) !== null) { M.splice(1, 1, tem[1]); } + return { name : M[0], version : M[1] }; + }(), + + supports_transform: function () { return 'transform' in document.createElement("div").style; }(), + + // SVG + + make_svg: make_svg, + seat_all_svg: seat_all_svg, + seat_svg: seat_svg, + + + // EVENTS + + delayed_callback : delayed_callback, + add_event_listener: add_active_event_listener, + remove_event_listener: remove_active_event_listener, + add_passive_event_listener: add_passive_event_listener, + remove_passive_event_listener: remove_passive_event_listener, + + // OTHER + + data: data, + store: store, + retrieve: retrieve, + merge: merge, + object_and: object_and, + object_sub: object_sub, + to_array: to_array, + warn: warn, + error: error, + log: log, + assign_warn: function(a) { + for (var i = 1; i < arguments.length; i++) { + var b = arguments[i]; + for (var key in b) if (b.hasOwnProperty(key)) { + if (a[key] === b[key]) { + TK.warn("overwriting identical", key, "(", a[key], ")"); + } else if (a[key]) { + TK.warn("overwriting", key, "(", a[key], "vs", b[key], ")"); + } + a[key] = b[key]; + } + } + + return a; + }, + print_widget_tree: print_widget_tree, +}; + +// POLYFILLS + +if (Array.isArray === void(0)) { + Array.isArray = function(obj) { + return Object.prototype.toString.call(obj) === '[object Array]'; + } +}; + +if (Object.assign === void(0)) { + Object.defineProperty(Object, 'assign', { + enumerable: false, + configurable: true, + writable: true, + value: function(target) { + 'use strict'; + if (target === void(0) || target === null) { + throw new TypeError('Cannot convert first argument to object'); + } + + var to = Object(target); + for (var i = 1; i < arguments.length; i++) { + var nextSource = arguments[i]; + if (nextSource === void(0) || nextSource === null) { + continue; + } + nextSource = Object(nextSource); + + var keysArray = Object.keys(Object(nextSource)); + for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) { + var nextKey = keysArray[nextIndex]; + var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey); + if (desc !== void(0) && desc.enumerable) { + to[nextKey] = nextSource[nextKey]; + } + } + } + return to; + } + }); +} + +if (!('remove' in Element.prototype)) { + Element.prototype.remove = function() { + this.parentNode.removeChild(this); + }; +} +})(this); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/README b/share/web_surfaces/builtin/mixer/toolkit/widgets/README new file mode 100644 index 0000000000..9235a14e0b --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/README @@ -0,0 +1,5 @@ +@category: Widgets + +Widgets are fully functional elements to build user interfaces. +They typically rely on other elements like #Modules and #Implements and +are somehow based on #Widget itself or other widgets. diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/button.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/button.js new file mode 100644 index 0000000000..2053a5dacd --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/button.js @@ -0,0 +1,114 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +"use strict"; +(function(w, TK){ + +TK.Button = TK.class({ + /** + * TK.Button is a simple, clickable widget containing an + * {@link TK.Icon} and a {@link TK.Label} to trigger functions. + * Button serves as base for other widgets, too, e.g. + * {@link TK.Toggle}, {@link TK.ConfirmButton} and {@link TK.Select}. + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {String|Boolean} [options.label=false] - Text for the + * button label. Set to false to remove the label + * from DOM. + * @property {String|Boolean} [options.icon=false] - URL to an image + * file or an icon class (see styles/fonts/Toolkit.html). If set + * to false, the icon is removed from DOM. + * @property {Boolean} [options.state=false] - State of the button, + * reflected as class toolkit-active. + * @property {String} [options.layout="horizontal"] - Define the + * arrangement of label and icon. vertical means icon + * above the label, horizontal places the icon left + * to the label. + * + * @extends TK.Widget + * + * @class TK.Button + */ + _class: "Button", + Extends: TK.Widget, + _options: Object.assign(Object.create(TK.Widget.prototype._options), { + label: "string|boolean", + icon: "string|boolean", + state: "boolean", + layout: "string", + }), + options: { + label: false, + icon: false, + state: false, + layout: "horizontal" + }, + initialize: function (options) { + var E; + TK.Widget.prototype.initialize.call(this, options); + /** + * @member {HTMLDivElement} TK.Button#element - The main DIV element. + * Has class toolkit-button. + */ + if (!(E = this.element)) this.element = E = TK.element("div"); + TK.add_class(E, "toolkit-button"); + this.widgetize(E, true, true, true); + }, + destroy: function () { + TK.Widget.prototype.destroy.call(this); + }, + + redraw: function() { + TK.Widget.prototype.redraw.call(this); + var I = this.invalid; + var O = this.options; + var E = this.element; + + if (I.layout) { + I.layout = false; + TK.toggle_class(E, "toolkit-vertical", O.layout === "vertical"); + TK.toggle_class(E, "toolkit-horizontal", O.layout !== "vertical"); + } + + if (I.state) { + I.state = false; + TK.toggle_class(E, "toolkit-active", O.state); + } + }, +}); +/** + * @member {TK.Icon} TK.Button#icon - The {@link TK.Icon} widget. + */ +TK.ChildWidget(TK.Button, "icon", { + create: TK.Icon, + option: "icon", + inherit_options: true, + toggle_class: true, +}); +/** + * @member {TK.Label} TK.Button#label - The {@link TK.Label} of the button. + */ +TK.ChildWidget(TK.Button, "label", { + create: TK.Label, + option: "label", + inherit_options: true, + toggle_class: true, +}); +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/buttonarray.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/buttonarray.js new file mode 100644 index 0000000000..09bfe39532 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/buttonarray.js @@ -0,0 +1,459 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + /** + * The useraction event is emitted when a widget gets modified by user interaction. + * The event is emitted for the option show. + * + * @event TK.Knob#useraction + * @param {string} name - The name of the option which was changed due to the users action. + * @param {mixed} value - The new value of the option. + */ + +"use strict"; +(function(w, TK){ +function hide_arrows() { + if (!this._prev.parentNode) return; + if (this._prev.parentNode) this._prev.remove(); + if (this._next.parentNode) this._next.remove(); + var E = this.element; + TK.remove_class(E, "toolkit-over"); + this.trigger_resize(); +} +function show_arrows() { + if (this._prev.parentNode) return; + var E = this.element; + E.insertBefore(this._prev, this._clip); + E.appendChild(this._next); + TK.add_class(E, "toolkit-over"); + this.trigger_resize(); +} +function prev_clicked(e) { + this.userset("show", Math.max(0, this.options.show - 1)); +} +function prev_dblclicked(e) { + this.userset("show", 0); +} + +function next_clicked(e) { + this.userset("show", Math.min(this.buttons.length-1, this.options.show + 1)); +} +function next_dblclicked(e) { + this.userset("show", this.buttons.length-1); +} + +function button_clicked(button) { + this.userset("show", this.buttons.indexOf(button)); +} +function easeInOut (t, b, c, d) { + t /= d/2; + if (t < 1) return c/2*t*t + b; + t--; + return -c/2 * (t*(t-2) - 1) + b; +} + +var zero = { width: 0, height: 0}; + +TK.ButtonArray = TK.class({ + /** + * TK.ButtonArray is a list of ({@link TK.Button})s, arranged + * either vertically or horizontally. TK.ButtonArray is able to + * add arrow buttons automatically if the overal size is less + * than the width/height of the buttons list. + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {Array} [options.buttons=[]] - A list of + * button options objects or label strings which is converted to + * button instances on init. If `get` is called, a converted list + * of button instances is returned. + * @property {Boolean} [options.auto_arrows=true] - Set to `false` + * to disable auto-generated arrow buttons on overflow. + * @property {String} [options.direction="horizontal"] - The layout + * of the button list, either "horizontal" or "vertical". + * @property {Integer|TK.Button} [options.show=-1] - The {@link TK.Button} + * to scroll to and highlight, expects either the button index starting + * from zero or the {@link TK.Button} instance itself. Set to `-1` to + * de-select any selected button. + * @property {Integer} [options.scroll=0] - Offer scrollbars for generic + * scrolling. This reduces performance because movement is done in JS + * instead of (pesumably accelerated) CSS transitions. 0 for standard + * behavior, n > 0 is handled as milliseconds for transitions. + * @property {Object} [options.button_class=TK.Button] - A class to + * be used for instantiating the buttons. + * + * @class TK.ButtonArray + * + * @extends TK.Container + */ + _class: "ButtonArray", + Extends: TK.Container, + _options: Object.assign(Object.create(TK.Container.prototype._options), { + buttons: "array", + auto_arrows: "boolean", + direction: "string", + show: "int", + resized: "boolean", + scroll: "int", + button_class: "TK.Button", + }), + options: { + buttons: [], + auto_arrows: true, + direction: "horizontal", + show: -1, + resized: false, + scroll: 0, + button_class: TK.Button, + }, + static_events: { + set_buttons: function(value) { + for (var i = 0; i < this.buttons.length; i++) + this.buttons[i].destroy(); + this.buttons = []; + this.add_buttons(value); + }, + set_direction: function(value) { + this.prev.set("label", value === "vertical" ? "\u25B2" : "\u25C0"); + this.next.set("label", value === "vertical" ? "\u25BC" : "\u25B6"); + }, + set_show: function(value) { + var button = this.current(); + if (button) { + button.set("state", true); + /** + * Is fired when a button is activated. + * + * @event TK.ButtonArray#changed + * + * @param {TK.Button} button - The {@link TK.Button} which was clicked. + * @param {int} id - the ID of the clicked {@link TK.Button}. + */ + this.fire_event("changed", button, value); + } + }, + }, + initialize: function (options) { + /** + * @member {Array} TK.ButtonArray#buttons - An array holding all {@link TK.Button}s. + */ + this.buttons = []; + TK.Container.prototype.initialize.call(this, options); + /** + * @member {HTMLDivElement} TK.ButtonArray#element - The main DIV container. + * Has class toolkit-buttonarray. + */ + TK.add_class(this.element, "toolkit-buttonarray"); + /** + * @member {HTMLDivElement} TK.ButtonArray#_clip - A clipping area containing the list of {@link TK.Button}s. + * Has class toolkit-clip. + */ + this._clip = TK.element("div", "toolkit-clip"); + /** + * @member {HTMLDivElement} TK.ButtonArray#_container - A container for all the {@link TK.Button}s. + * Has class toolkit-container. + */ + this._container = TK.element("div", "toolkit-container"); + this.element.appendChild(this._clip); + this._clip.appendChild(this._container); + + var vert = this.get("direction") === "vertical"; + + /** + * @member {TK.Button} TK.ButtonArray#prev - The previous arrow {@link TK.Button} instance. + */ + this.prev = new TK.Button({class: "toolkit-previous", dblclick:400}); + /** + * @member {TK.Button} TK.ButtonArray#next - The next arrow {@link TK.Button} instance. + */ + this.next = new TK.Button({class: "toolkit-next", dblclick:400}); + + this.prev.add_event("click", prev_clicked.bind(this)); + this.prev.add_event("doubleclick", prev_dblclicked.bind(this)); + this.next.add_event("click", next_clicked.bind(this)); + this.next.add_event("doubleclick", next_dblclicked.bind(this)); + + /** + * @member {HTMLDivElement} TK.ButtonArray#_prev - The HTMLDivElement of the previous {@link TK.Button}. + */ + this._prev = this.prev.element; + /** + * @member {HTMLDivElement} TK.ButtonArray#_next - The HTMLDivElement of the next {@link TK.Button}. + */ + this._next = this.next.element; + + this.set("direction", this.options.direction); + this.set("scroll", this.options.scroll); + this.add_children([this.prev, this.next]); + this.add_buttons(this.options.buttons); + this._sizes = null; + }, + + resize: function () { + var tmp, e; + + var os = this._sizes; + var s = { + container: this._container.getBoundingClientRect(), + clip: { + height: TK.inner_height(this._clip), + width: TK.inner_width(this._clip), + }, + buttons: [], + buttons_pos: [], + prev: this._prev.parentNode ? this._prev.getBoundingClientRect() : os ? os.prev : zero, + next: this._next.parentNode ? this._next.getBoundingClientRect() : os ? os.next : zero, + element: this.element.getBoundingClientRect(), + }; + + this._sizes = s; + + for (var i = 0; i < this.buttons.length; i++) { + e = this.buttons[i].element; + s.buttons[i] = e.getBoundingClientRect(); + s.buttons_pos[i] = { left: e.offsetLeft, top: e.offsetTop }; + } + + TK.Container.prototype.resize.call(this); + }, + + /** + * Adds an array of buttons to the end of the list. + * + * @method TK.ButtonArray#add_buttons + * + * @param {Array.} options - An Array containing objects + * with options for the buttons (see {@link TK.Button} for more + * information) or strings for the buttons labels. + */ + add_buttons: function (options) { + for (var i = 0; i < options.length; i++) + this.add_button(options[i]); + }, + + /** + * Adds a {@link TK.Button} to the TK.ButtonArray. + * + * @method TK.ButtonArray#add_button + * + * @param {Object|string} options - An object containing options for the + * {@link TK.Button} to add or a string for the label. + * @param {integer} [position] - The position to add the {@link TK.Button} + * to. If `undefined`, the {@link TK.Button} is added to the end of the list. + * + * @returns {TK.Button} The {@link TK.Button} instance. + */ + add_button: function (options, position) { + if (typeof options === "string") + options = {label: options} + var b = new this.options.button_class(options); + var len = this.buttons.length; + var vert = this.options.direction === "vertical"; + if (position === void(0)) + position = this.buttons.length; + if (position === len) { + this.buttons.push(b); + this._container.appendChild(b.element); + } else { + this.buttons.splice(position, 0, b); + this._container.insertBefore(b.element, + this._container.childNodes[position]); + } + + this.add_child(b); + + this.trigger_resize(); + b.add_event("click", button_clicked.bind(this, b)); + /** + * A {@link TK.Button} was added to the TK.ButtonArray. + * + * @event TK.ButtonArray#added + * + * @param {TK.Button} button - The {@link TK.Button} which was added to TK.ButtonArray. + */ + if (b === this.current()) + b.set("state", true); + this.fire_event("added", b); + + return b; + }, + /** + * Removes a {@link TK.Button} from the TK.ButtonArray. + * + * @method TK.ButtonArray#remove_button + * + * @param {integer|TK.Button} button - button index or the {@link TK.Button} + * instance to be removed. + */ + remove_button: function (button) { + if (typeof button === "object") + button = this.buttons.indexOf(button); + if (button < 0 || button >= this.buttons.length) + return; + /** + * A {@link TK.Button} was removed from the TK.ButtonArray. + * + * @event TK.ButtonArray#removed + * + * @param {TK.Button} button - The {@link TK.Button} instance which was removed. + */ + this.fire_event("removed", this.buttons[button]); + if (this.current() && button <= this.options.show) { + this.options.show --; + this.invalid.show = true; + this.trigger_draw(); + } + this.buttons[button].destroy(); + this.buttons.splice(button, 1); + this.trigger_resize(); + }, + + destroy: function () { + for (var i = 0; i < this.buttons.length; i++) + this.buttons[i].destroy(); + this.prev.destroy(); + this.next.destroy(); + this._container.remove(); + this._clip.remove(); + TK.Container.prototype.destroy.call(this); + }, + + redraw: function() { + TK.Container.prototype.redraw.call(this); + var I = this.invalid; + var O = this.options; + var S = this._sizes; + + if (I.direction) { + var E = this.element; + TK.remove_class(E, "toolkit-vertical", "toolkit-horizontal"); + TK.add_class(E, "toolkit-"+O.direction); + } + + if (I.validate("direction", "auto_arrows") || I.resized) { + if (O.auto_arrows && O.resized && !O.needs_resize) { + var dir = O.direction === "vertical"; + var subd = dir ? 'top' : 'left'; + var subs = dir ? 'height' : 'width'; + + var clipsize = S.clip[subs]; + var listsize = 0; + + if (this.buttons.length) + listsize = S.buttons_pos[this.buttons.length-1][subd] + + S.buttons[this.buttons.length-1][subs]; + if (Math.round(listsize) > Math.round(clipsize)) { + show_arrows.call(this); + } else if (Math.round(listsize) <= Math.round(clipsize)) { + hide_arrows.call(this); + } + } else if (!O.auto_arrows) { + hide_arrows.call(this); + } + } + if (I.validate("show", "direction", "resized")) { + if (O.resized && !O.needs_resize) { + var show = O.show + if (show >= 0 && show < this.buttons.length) { + /* move the container so that the requested button is shown */ + var dir = O.direction === "vertical"; + var subd = dir ? 'top' : 'left'; + var subt = dir ? 'scrollTop' : 'scrollLeft'; + var subs = dir ? 'height' : 'width'; + + var btnrect = S.buttons[show]; + var clipsize = S.clip[subs]; + var listsize = 0; + var btnsize = 0; + var btnpos = 0; + if (S.buttons.length) { + listsize = S.buttons_pos[this.buttons.length-1][subd] + + S.buttons[this.buttons.length-1][subs]; + btnsize = S.buttons[show][subs]; + btnpos = S.buttons_pos[show][subd]; + } + + var p = (Math.max(0, Math.min(listsize - clipsize, btnpos - (clipsize / 2 - btnsize / 2)))); + if (this.options.scroll) { + var s = this._clip[subt]; + this._scroll = {to: ~~p, from: s, dir: p > s ? 1 : -1, diff: ~~p - s, time: Date.now()}; + this.invalid.scroll = true; + if (this._container.style[subd]) + this._container.style[subd] = null; + } else { + this._container.style[subd] = -p + "px"; + if (s) + this._clip[subt] = 0; + } + } + } + } + if (this.invalid.scroll && this._scroll) { + var subt = O.direction === "vertical" ? 'scrollTop' : 'scrollLeft'; + var s = ~~this._clip[subt]; + var _s = this._scroll; + var now = Date.now(); + if ((s >= _s.to && _s.dir > 0) + || (s <= _s.to && _s.dir < 0) + || now > (_s.time + O.scroll)) { + this.invalid.scroll = false; + this._clip[subt] = _s.to; + } else { + this._clip[subt] = easeInOut(Date.now() - _s.time, _s.from, _s.diff, O.scroll); + this.trigger_draw_next(); + } + } + }, + + /** + * The currently active button. + * + * @method TK.ButtonArray#current + * + * @returns {TK.Button} The active {@link TK.Button} or null, if none is selected. + */ + current: function() { + var n = this.options.show; + if (n >= 0 && n < this.buttons.length) { + return this.buttons[n]; + } + return null; + }, + + set: function (key, value) { + var button; + if (key === "show") { + //if (value < 0) value = 0; + //if (value >= this.buttons.length) value = this.buttons.length - 1; + if (value === this.options.show) return value; + + button = this.current(); + if (button) button.set("state", false); + } + if (key == "scroll") { + TK[value>0?"add_class":"remove_class"](this.element, "toolkit-scroll"); + this.trigger_resize(); + } + return TK.Container.prototype.set.call(this, key, value); + }, + get: function (key) { + if (key === "buttons") return this.buttons; + return TK.Container.prototype.get.call(this, key); + } +}); +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/chart.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/chart.js new file mode 100644 index 0000000000..9c8846c552 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/chart.js @@ -0,0 +1,839 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function(w, TK){ + +function calculate_overlap(X, Y) { + /* no overlap, return 0 */ + if (X[2] < Y[0] || Y[2] < X[0] || X[3] < Y[1] || Y[3] < X[1]) return 0; + + return (Math.min(X[2], Y[2]) - Math.max(X[0], Y[0])) * + (Math.min(X[3], Y[3]) - Math.max(X[1], Y[1])); +} + +function show_handles() { + var handles = this.handles; + + for (var i = 0; i < handles.length; i++) { + this.add_child(handles[i]); + } +} + +function hide_handles() { + var handles = this.handles; + + for (var i = 0; i < handles.length; i++) { + this.remove_child(handles[i]); + } +} + +var STOP = function(e) { + e.preventDefault(); + e.stopPropagation(); + return false; +} +function draw_key() { + var __key, bb; + + var _key = this._key; + var _key_bg = this._key_background; + + if (!_key || !_key_bg) return; + + while (_key.firstChild !== _key.lastChild) + _key.removeChild(_key.lastChild); + + TK.empty(_key.firstChild); + + var O = this.options; + + var disp = "none"; + var gpad = TK.css_space(_key, "padding"); + var gmarg = TK.css_space(_key, "margin"); + var c = 0; + var w = 0; + var top = 0; + var lines = []; + for (var i = 0; i < this.graphs.length; i++) { + if (this.graphs[i].get("key") !== false) { + var t = TK.make_svg("tspan", {"class": "toolkit-label", + style: "dominant-baseline: central;" + }); + t.textContent = this.graphs[i].get("key"); + t.setAttribute("x", gpad.left); + _key.firstChild.appendChild(t); + + if (!bb) bb = _key.getBoundingClientRect(); + top += c ? parseInt(TK.get_style(t, "line-height")) : gpad.top; + t.setAttribute("y", top + bb.height / 2); + + lines.push({ + x: (parseInt(TK.get_style(t, "margin-right")) || 0), + y: Math.round(top), + width: Math.round(bb.width), + height: Math.round(bb.height), + "class": this.graphs[i].element.getAttribute("class"), + color: (this.graphs[i].element.getAttribute("color") || ""), + style: this.graphs[i].element.getAttribute("style") + }) + w = Math.max(w, t.getComputedTextLength()); + disp = "block"; + c++; + } + } + for (var i = 0; i < lines.length; i++) { + var b = TK.make_svg("rect", { + "class": lines[i]["class"] + " toolkit-rect", + color: lines[i].color, + style: lines[i].style, + x: lines[i].x + 0.5 + w + gpad.left, + y: lines[i].y + 0.5 + parseInt(lines[i].height / 2 - O.key_size.y / 2), + height: O.key_size.y, + width: O.key_size.x + }); + _key.appendChild(b); + } + _key_bg.style.display = disp; + _key.style.display = disp; + + bb = _key.getBoundingClientRect(); + var width = this.range_x.options.basis; + var height = this.range_y.options.basis; + + switch (O.key) { + case "top-left": + __key = { + x1: gmarg.left, + y1: gmarg.top, + x2: gmarg.left + parseInt(bb.width) + gpad.left + gpad.right, + y2: gmarg.top + parseInt(bb.height) + gpad.top + gpad.bottom + } + break; + case "top-right": + __key = { + x1: width - gmarg.right - parseInt(bb.width) - gpad.left - gpad.right, + y1: gmarg.top, + x2: width - gmarg.right, + y2: gmarg.top + parseInt(bb.height) + gpad.top + gpad.bottom + } + break; + case "bottom-left": + __key = { + x1: gmarg.left, + y1: height - gmarg.bottom - parseInt(bb.height) - gpad.top - gpad.bottom, + x2: gmarg.left + parseInt(bb.width) + gpad.left + gpad.right, + y2: height - gmarg.bottom + } + break; + case "bottom-right": + __key = { + x1: width - gmarg.right - parseInt(bb.width) - gpad.left - gpad.right, + y1: height -gmarg.bottom - parseInt(bb.height) - gpad.top - gpad.bottom, + x2: width - gmarg.right, + y2: height - gmarg.bottom + } + break; + default: + TK.warn("Unsupported key", O.key); + } + _key.setAttribute("transform", "translate(" + __key.x1 + "," + __key.y1 + ")"); + _key_bg.setAttribute("x", __key.x1); + _key_bg.setAttribute("y", __key.y1); + _key_bg.setAttribute("width", __key.x2 - __key.x1); + _key_bg.setAttribute("height", __key.y2 - __key.y1); +} +function draw_title() { + var _title = this._title; + if (!_title) return; + + _title.textContent = this.options.title; + + /* FORCE_RELAYOUT */ + TK.S.add(function() { + var mtop = parseInt(TK.get_style(_title, "margin-top") || 0); + var mleft = parseInt(TK.get_style(_title, "margin-left") || 0); + var mbottom = parseInt(TK.get_style(_title, "margin-bottom") || 0); + var mright = parseInt(TK.get_style(_title, "margin-right") || 0); + var bb = _title.getBoundingClientRect(); + var x,y,anchor, range_x = this.range_x, range_y = this.range_y; + switch (this.options.title_position) { + case "top-left": + anchor = "start"; + x = mleft; + y = mtop + bb.height / 2; + break; + case "top": + anchor = "middle"; + x = range_x.options.basis / 2; + y = mtop + bb.height / 2; + break; + case "top-right": + anchor = "end"; + x = range_x.options.basis - mright; + y = mtop + bb.height / 2; + break; + case "left": + anchor = "start"; + x = mleft; + y = range_y.options.basis / 2; + break; + case "center": + anchor = "middle"; + x = range_x.options.basis / 2; + y = range_y.options.basis / 2; + break; + case "right": + anchor = "end"; + x = range_x.options.basis - mright; + y = range_y.options.basis / 2; + break; + case "bottom-left": + anchor = "start"; + x = mleft; + y = range_y.options.basis - mtop - bb.height / 2; + break; + case "bottom": + anchor = "middle"; + x = range_x.options.basis / 2; + y = range_y.options.basis - mtop - bb.height / 2; + break; + case "bottom-right": + anchor = "end"; + x = range_x.options.basis - mright; + y = range_y.options.basis - mtop - bb.height / 2; + break; + default: + TK.warn("Unsupported title_position", this.options.title_position); + } + TK.S.add(function() { + _title.setAttribute("text-anchor", anchor); + _title.setAttribute("x", x); + _title.setAttribute("y", y); + }, 1); + }.bind(this)); +} + +/** + * TK.Chart is an SVG image containing one or more Graphs. TK.Chart + * extends {@link TK.Widget} and contains a {@link TK.Grid} and two + * {@link TK.Range}s. + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {String|Boolean} [options.title=""] - A title for the Chart. + * Set to `false` to remove the title from the DOM. + * @property {String} [options.title_position="top-right"] - Position of the + * title inside of the chart. Possible values are + * "top-left", "top", "top-right", + * "left", "center", "right", + * "bottom-left", "bottom" and + * "bottom-right". + * @property {Boolean|String} [options.key=false] - If set to a string + * a key is rendered into the chart at the given position. The key + * will detail names and colors of the graphs inside of this chart. + * Possible values are "top-left", "top-right", + * "bottom-left" and "bottom-right". Set to `false` + * to remove the key from the DOM. + * @property {Object} [options.key_size={x:20,y:10}] - Size of the colored + * rectangles inside of the key describing individual graphs. + * @property {Array} [options.grid_x=[]] - An array containing + * objects with the following optional members to draw the grid: + * @property {Number} [options.grid_x.pos] - The value where to draw grid line and corresponding label. + * @property {String} [options.grid_x.color] - A valid CSS color string to colorize the elements. + * @property {String} [options.grid_x.class] - A class name for the elements. + * @property {String} [options.grid_x.label] - A label string. + * @property {Array} [options.grid_y=[]] - An array containing + * objects with the following optional members to draw the grid: + * @property {Number} [options.grid_y.pos] - The value where to draw grid line and corresponding label. + * @property {String} [options.grid_y.color] - A valid CSS color string to colorize the elements. + * @property {String} [options.grid_y.class] - A class name for the elements. + * @property {String} [options.grid_y.label] - A label string. + * @property {Boolean} [options.show_grid=true] - Set to false to + * hide the grid. + * @property {Function|Object} [options.range_x={}] - Either a function + * returning a {@link TK.Range} or an object containing options for a + * new {@link TK.Range}. + * @property {Function|Object} [options.range_y={}] - Either a function + * returning a {@link TK.Range} or an object containing options for a + * new {@link TK.Range}. + * @property {Object|Function} [options.range_z={ scale: "linear", min: 0, max: 1 }] - + * Either a function returning a {@link TK.Range} or an object + * containing options for a new {@link TK.Range}. + * @property {Number} [options.importance_label=4] - Multiplicator of + * square pixels on hit testing labels to gain importance. + * @property {Number} [options.importance_handle=1] - Multiplicator of + * square pixels on hit testing handles to gain importance. + * @property {Number} [options.importance_border=50] - Multiplicator of + * square pixels on hit testing borders to gain importance. + * @property {Array} [options.handles=[]] - An array of options for + * creating {@link TK.ResponseHandle} on init. + * @property {Boolean} [options.show_handles=true] - Show or hide all + * handles. + * + * @class TK.Chart + * + * @extends TK.Widget + */ +function geom_set(value, key) { + this.set_style(key, value+"px"); + TK.error("using deprecated '"+key+"' options"); +} +TK.Chart = TK.class({ + _class: "Chart", + Extends: TK.Widget, + Implements: TK.Ranges, + _options: Object.assign(Object.create(TK.Widget.prototype._options), { + width: "int", + height: "int", + _width: "int", + _height: "int", + range_x: "object", + range_y: "object", + range_z: "object", + key: "string", + key_size: "object", + title: "string", + title_position: "string", + resized: "boolean", + + importance_label: "number", + importance_handle: "number", + importance_border: "number", + handles: "array", + show_handles: "boolean", + depth: "number", + }), + options: { + grid_x: [], + grid_y: [], + range_x: {}, // an object with options for a range for the x axis + // or a function returning a TK.Range instance (only on init) + range_y: {}, // an object with options for a range for the y axis + // or a function returning a TK.Range instance (only on init) + range_z: { scale: "linear", min: 0, max: 1 }, // TK.Range z options + key: false, // key draws a description for the graphs at the given + // position, use false for no key + key_size: {x:20, y:10}, // size of the key rects + title: "", // a title for the chart + title_position: "top-right", // the position of the title + resized: false, + + importance_label: 4, // multiplicator of square pixels on hit testing + // labels to gain importance + importance_handle: 1, // multiplicator of square pixels on hit testing + // handles to gain importance + importance_border: 50, // multiplicator of square pixels on hit testing + // borders to gain importance + handles: [], // list of bands to create on init + show_handles: true, + }, + static_events: { + set_width: geom_set, + set_height: geom_set, + + mousewheel: STOP, + DOMMouseScroll: STOP, + set_depth: function(value) { + this.range_z.set("basis", value); + }, + set_show_handles: function(value) { + (value ? show_handles : hide_handles).call(this); + }, + }, + initialize: function (options) { + var E, S; + /** + * @member {Array} TK.Chart#graphs - An array containing all SVG paths acting as graphs. + */ + this.graphs = []; + /** + * @member {Array} TK.Chart#handles - An array containing all {@link TK.ResponseHandle} instances. + */ + this.handles = []; + TK.Widget.prototype.initialize.call(this, options); + + /** + * @member {TK.Range} TK.Chart#range_x - The {@link TK.Range} for the x axis. + */ + /** + * @member {TK.Range} TK.Chart#range_y - The {@link TK.Range} for the y axis. + */ + this.add_range(this.options.range_x, "range_x"); + this.add_range(this.options.range_y, "range_y"); + this.add_range(this.options.range_z, "range_z"); + this.range_y.set("reverse", true, true, true); + + /** + * @member {HTMLDivElement} TK.Chart#element - The main DIV container. + * Has class toolkit-chart. + */ + if (!(E = this.element)) this.element = E = TK.element("div"); + TK.add_class(E, "toolkit-chart"); + this.widgetize(E, true, true, true); + + this.svg = S = TK.make_svg("svg"); + + if (!this.options.width) + this.options.width = this.range_x.options.basis; + if (!this.options.height) + this.options.height = this.range_y.options.basis; + + /** + * @member {SVGGroup} TK.Chart#_graphs - The SVG group containing all graphs. + * Has class toolkit-graphs. + */ + this._graphs = TK.make_svg("g", {"class": "toolkit-graphs"}); + S.appendChild(this._graphs); + E.appendChild(S); + + if (this.options.width) this.set("width", this.options.width); + if (this.options.height) this.set("height", this.options.height); + + /** + * @member {SVGGroup} TK.Chart#_handles - The SVG group containing all handles. + * Has class toolkit-handles. + */ + this._handles = TK.make_svg("g", {"class": "toolkit-handles"}); + this.svg.appendChild(this._handles); + this.svg.onselectstart = function () { return false; }; + this.add_handles(this.options.handles); + }, + resize: function () { + var E = this.element; + var O = this.options; + var S = this.svg; + + TK.Widget.prototype.resize.call(this); + + var tmp = TK.css_space(S, "border", "padding"); + var w = TK.inner_width(E) - tmp.left - tmp.right; + var h = TK.inner_height(E) - tmp.top - tmp.bottom; + + if (w > 0 && O._width !== w) { + this.set("_width", w); + this.range_x.set("basis", w); + this.invalid._width = true; + this.trigger_draw(); + } + if (h > 0 && O._height !== h) { + this.set("_height", h); + this.range_y.set("basis", h); + this.invalid._height = true; + this.trigger_draw(); + } + }, + redraw: function () { + var I = this.invalid; + var E = this.svg; + var O = this.options; + + TK.Widget.prototype.redraw.call(this); + + if (I.validate("ranges", "_width", "_height", "range_x", "range_y")) { + /* we need to redraw both key and title, because + * they do depend on the size */ + I.title = true; + I.key = true; + var w = O._width; + var h = O._height; + if (w && h) { + E.setAttribute("width", w + "px"); + E.setAttribute("height", h + "px"); + } + } + + if (I.graphs) { + for (var i = 0; i < this.graphs.length; i++) { + this.graphs[i].redraw(); + } + } + if (I.validate("title", "title_position")) { + draw_title.call(this); + } + if (I.validate("key", "key_size", "graphs")) { + draw_key.call(this); + } + if (I.show_handles) { + I.show_handles = false; + if (O.show_handles) { + this._handles.style.removeProperty("display"); + } else { + this._handles.style.display = "none"; + } + } + }, + destroy: function () { + for (var i = 0; i < this._graphs.length; i++) { + this._graphs[i].destroy(); + } + this._graphs.remove(); + this._handles.remove(); + TK.Widget.prototype.destroy.call(this); + }, + add_child: function(child) { + if (child instanceof TK.Graph) { + this.add_graph(child); + return; + } + + TK.Widget.prototype.add_child.call(this, child); + }, + remove_child: function(child) { + if (child instanceof TK.Graph) { + this.remove_graph(child); + return; + } + + TK.Widget.prototype.remove_child.call(this, child); + }, + /** + * Add a graph to the chart. + * + * @method TK.Chart#add_graph + * + * @param {Object} graph - The graph to add. This can be either an + * instance of {@link TK.Graph} or an object of options to + * {@link TK.Graph}. + * + * @returns {Object} The instance of {@link TK.Graph}. + * + * @emits TK.Chart#graphadded + */ + add_graph: function (options) { + var g; + + if (TK.Graph.prototype.isPrototypeOf(options)) { + g = options; + } else { + g = new TK.Graph(options); + } + + g.set("container", this._graphs); + if (!g.options.range_x) g.set("range_x", this.range_x); + if (!g.options.range_y) g.set("range_y", this.range_y); + + this.graphs.push(g); + g.add_event("set", function (key, value, obj) { + if (key === "color" || key === "class" || key === "key") { + this.invalid.graphs = true; + this.trigger_draw(); + } + }.bind(this)); + /** + * Is fired when a graph was added. Arguments are the graph + * and its position in the array. + * + * @event TK.Chart#graphadded + * + * @param {TK.Graph} graph - The {@link TK.Graph} which was added. + * @param {int} id - The ID of the added {@link TK.Graph}. + */ + this.fire_event("graphadded", g, this.graphs.length - 1); + + this.invalid.graphs = true; + this.trigger_draw(); + TK.Widget.prototype.add_child.call(this, g); + return g; + }, + /** + * Remove a graph from the chart. + * + * @method TK.Chart#remove_graph + * + * @param {TK.Graph} graph - The {@link TK.Graph} to remove. + * + * @emits TK.Chart#graphremoved + */ + remove_graph: function (g) { + var i; + if ((i = this.graphs.indexOf(g)) !== -1) { + /** + * Is fired when a graph was removed. Arguments are the graph + * and its position in the array. + * + * @event TK.Chart#graphremoved + * + * @param {TK.Graph} graph - The {@link TK.Graph} which was removed. + * @param {int} id - The ID of the removed {@link TK.Graph}. + */ + this.fire_event("graphremoved", g, i); + g.destroy(); + this.graphs.splice(i, 1); + TK.Widget.prototype.remove_child.call(this, g); + this.invalid.graphs = true; + this.trigger_draw(); + } + }, + /** + * Remove all graphs from the chart. + * + * @method TK.Chart#empty + * + * @emits TK.Chart#emptied + */ + empty: function () { + this.graphs.map(this.remove_graph, this); + /** + * Is fired when all graphs are removed from the chart. + * + * @event TK.Chart#emptied + */ + this.fire_event("emptied"); + }, + + /** + * Add a new handle to the widget. Options is an object containing + * options for the {@link TK.ResponseHandle}. + * + * @method TK.Chart#add_handle + * + * @param {Object} [options={ }] - An object containing initial options. - The options for the {@link TK.ResponseHandle}. + * @param {Object} [type=TK.ResponseHandle] - A widget class to be used as the new handle. + * + * @emits TK.Chart#handleadded + */ + add_handle: function (options, type) { + type = type || TK.ResponseHandle; + options.container = this._handles; + if (options.range_x === void(0)) + options.range_x = function () { return this.range_x; }.bind(this); + if (options.range_y === void(0)) + options.range_y = function () { return this.range_y; }.bind(this); + if (options.range_z === void(0)) + options.range_z = function () { return this.range_z; }.bind(this); + + options.intersect = this.intersect.bind(this); + + var h = new type(options); + this.handles.push(h); + if (this.options.show_handles) + this.add_child(h); + /** + * Is fired when a new handle was added. + * + * @param {TK.ResponseHandle} handle - The {@link TK.ResponseHandle} which was added. + * + * @event TK.Chart#handleadded + */ + this.fire_event("handleadded", h); + return h; + }, + /** + * Add multiple new {@link TK.ResponseHandle} to the widget. Options is an array + * of objects containing options for the new instances of {@link TK.ResponseHandle}. + * + * @method TK.Chart#add_handles + * + * @param {Array} options - An array of options objects for the {@link TK.ResponseHandle}. + * @param {Object} [type=TK.ResponseHandle] - A widget class to be used for the new handles. + */ + add_handles: function (handles, type) { + for (var i = 0; i < handles.length; i++) + this.add_handle(handles[i], type); + }, + /** + * Remove a handle from the widget. + * + * @method TK.Chart#remove_handle + * + * @param {TK.ResponseHandle} handle - The {@link TK.ResponseHandle} to remove. + * + * @emits TK.Chart#handleremoved + */ + remove_handle: function (handle) { + // remove a handle from the widget. + for (var i = 0; i < this.handles.length; i++) { + if (this.handles[i] === handle) { + if (this.options.show_handles) + this.remove_child(handle); + this.handles[i].destroy(); + this.handles.splice(i, 1); + /** + * Is fired when a handle was removed. + * + * @event TK.Chart#handleremoved + */ + this.fire_event("handleremoved"); + break; + } + } + }, + /** + * Remove multiple or all {@link TK.ResponseHandle} from the widget. + * + * @method TK.Chart#remove_handles + * + * @param {Array} handles - An array of + * {@link TK.ResponseHandle} instances. If the argument reveals to + * `false`, all handles are removed from the widget. + */ + remove_handles: function (handles) { + var H = handles || this.handles.slice(); + for (var i = 0; i < H.length; i++) { + this.remove_handle(H[i]); + } + if (!handles) { + this.handles = []; + /** + * Is fired when all handles are removed. + * + * @event TK.Chart#emptied + */ + this.fire_event("emptied"); + } + }, + + intersect: function (X, handle) { + // this function walks over all known handles and asks for the coords + // of the label and the handle. Calculates intersecting square pixels + // according to the importance set in options. Returns an object + // containing intersect (the amount of intersecting square pixels) and + // count (the amount of overlapping elements) + var c = 0; + var a = 0, _a; + var O = this.options; + var importance_handle = O.importance_handle + var importance_label = O.importance_label + + for (var i = 0; i < this.handles.length; i++) { + var h = this.handles[i]; + if (h === handle || !h.get("active") || !h.get("show_handle")) continue; + _a = calculate_overlap(X, h.handle); + + if (_a) { + c ++; + a += _a * importance_handle; + } + + _a = calculate_overlap(X, h.label); + + if (_a) { + c ++; + a += _a * importance_label; + } + } + if (this.bands && this.bands.length) { + for (var i = 0; i < this.bands.length; i++) { + var b = this.bands[i]; + if (b === handle || !b.get("active") || !b.get("show_handle")) continue; + _a = calculate_overlap(X, b.handle); + + if (_a > 0) { + c ++; + a += _a * importance_handle; + } + + _a = calculate_overlap(X, b.label); + if (_a > 0) { + c ++; + a += _a * importance_label; + } + } + } + /* calculate intersection with border */ + _a = calculate_overlap(X, [ 0, 0, this.range_x.options.basis, this.range_y.options.basis ]); + a += ((X[2] - X[0]) * (X[3] - X[1]) - _a) * O.importance_border; + return {intersect: a, count: c}; + }, +}); +/** + * @member {TK.Grid} TK.Chart#grid - The grid element of the chart. + * Has class toolkit-grid. + */ +TK.ChildWidget(TK.Chart, "grid", { + create: TK.Grid, + show: true, + append: function() { + this.svg.insertBefore(this.grid.element, this.svg.firstChild); + }, + map_options: { + grid_x: "grid_x", + grid_y: "grid_y", + }, + default_options: function () { + return { + range_x: this.range_x, + range_y: this.range_y, + }; + }, +}); +function key_hover_cb(ev) { + var b = ev.type === "mouseenter"; + TK.toggle_class(this, "toolkit-hover", b); + /* this.nextSibling is the key */ + TK.toggle_class(this.nextSibling, "toolkit-hover", b); +} +/** + * @member {SVGRect} TK.Chart#_key_background - The SVG rectangle of the key. + * Has class toolkit-background. + */ +TK.ChildElement(TK.Chart, "key_background", { + option: "key", + display_check: function(v) { + return !!v; + }, + create: function() { + var k = TK.make_svg("rect", {"class": "toolkit-background"}); + k.addEventListener("mouseenter", key_hover_cb); + k.addEventListener("mouseleave", key_hover_cb); + return k; + }, + append: function() { + this.svg.appendChild(this._key_background); + }, +}); +/** + * @member {SVGGroup} TK.Chart#_key - The SVG group containing all descriptions. + * Has class toolkit-key. + */ +TK.ChildElement(TK.Chart, "key", { + option: "key", + display_check: function(v) { + return !!v; + }, + create: function() { + var key = TK.make_svg("g", {"class": "toolkit-key"}); + key.appendChild(TK.make_svg("text", {"class": "toolkit-key-text"})); + return key; + }, + append: function() { + this.svg.appendChild(this._key); + }, +}); +/** + * @member {SVGText} TK.Chart#_title - The title of the chart. + * Has class toolkit-title. + */ +TK.ChildElement(TK.Chart, "title", { + option: "title", + display_check: function(v) { + return typeof(v) === "string" && v.length; + }, + create: function() { + return TK.make_svg("text", { + "class": "toolkit-title", + style: "dominant-baseline: central;" + }); + }, + append: function() { + var svg = this.svg; + svg.insertBefore(this._title, svg.firstChild); + }, +}); +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/clock.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/clock.js new file mode 100644 index 0000000000..89a12d97fc --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/clock.js @@ -0,0 +1,382 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function(w, TK){ +var format_viewbox = TK.FORMAT("0 0 %d %d"); +function draw_time(force) { + var tmp, drawn; + var O = this.options; + var t = O.time; + + if ((tmp = t.getSeconds()) !== this.__sec || force) { + this.circulars.seconds.set("value", tmp); + this.__sec = tmp; + } + if ((tmp = t.getMinutes()) !== this.__min || force) { + this.circulars.minutes.set("value", tmp); + this.__min = tmp; + } + if ((tmp = t.getHours() % 12) !== this.__hour || force) { + this.circulars.hours.set("value", tmp); + this.__hour = tmp; + } + + var args = [t, + t.getFullYear(), + t.getMonth(), + t.getDate(), + t.getDay(), + t.getHours(), + t.getMinutes(), + t.getSeconds(), + t.getMilliseconds(), + Math.round(t.getMilliseconds() / (1000 / O.fps)), + O.months, + O.days]; + if ((tmp = O.label.apply(this, args)) !== this.__label || force) { + TK.set_text(this._label, tmp); + this.__label = tmp; + drawn = true; + } + if ((tmp = O.label_upper.apply(this, args)) !== this.__upper || force) { + TK.set_text(this._label_upper, tmp); + this.__upper = tmp; + drawn = true; + } + if ((tmp = O.label_lower.apply(this, args)) !== this.__lower || force) { + TK.set_text(this._label_lower, tmp); + this.__lower = tmp; + drawn = true; + } + + if (drawn) + /** + * Is fired when the time was drawn. + * + * @param {Date} time - The time which was drawn. + * + * @event TK.Clock#timedrawn + */ + this.fire_event("timedrawn", O.time); +} +function set_labels() { + var O = this.options; + var E = this._label; + var s = O.label(new Date(2000, 8, 30, 24, 59, 59, 999), 2000, 8, + 30, 6, 24, 59, 59, 999, 999, + O.months, O.days); + TK.set_text(E, s); + + E.setAttribute("transform", ""); + + /* FORCE_RELAYOUT */ + + TK.S.add(function() { + var bb = E.getBoundingClientRect(); + if (bb.width === 0) return; // we are hidden + var mleft = parseInt(TK.get_style(E, "margin-left")) || 0; + var mright = parseInt(TK.get_style(E, "margin-right")) || 0; + var mtop = parseInt(TK.get_style(E, "margin-top")) || 0; + var mbottom = parseInt(TK.get_style(E, "margin-bottom")) || 0; + var space = O.size - mleft - mright - this._margin * 2; + var scale = space / bb.width; + var pos = O.size / 2; + + TK.S.add(function() { + E.setAttribute("transform", "translate(" + pos + "," + pos + ") " + + "scale(" + scale + ")"); + + /* FORCE_RELAYOUT */ + + TK.S.add(function() { + bb = E.getBoundingClientRect(); + + TK.S.add(function() { + this._label_upper.setAttribute("transform", "translate(" + pos + "," + (pos - bb.height / 2 - mtop) + ") " + + "scale(" + (scale * O.label_scale) + ")"); + this._label_lower.setAttribute("transform", "translate(" + pos + "," + (pos + bb.height / 2 + mtop) + ") " + + "scale(" + (scale * O.label_scale) + ")"); + draw_time.call(this, true); + }.bind(this), 1); + }.bind(this)); + }.bind(this), 1); + }.bind(this)); +} +function timeout() { + if (this.__to) + window.clearTimeout(this.__to); + var O = this.options; + if (!O) return; + if (O.timeout) { + var d = O.time; + var ts = +Date.now(); + + if (O.offset) { + ts += (O.offset|0); + } + + d.setTime(ts); + this.set("time", d); + + var targ = (O.timeout|0); + if (O.timeadd) { + targ += (O.timeadd|0) - ((ts % 1000)|0) + } + this.__to = window.setTimeout(this.__timeout, targ); + } else this.__to = false; +} +function onhide() { + if (this.__to) { + window.clearTimeout(this.__to); + this.__to = false; + } +} + +TK.Clock = TK.class({ + /** + * TK.Clock shows a customized clock with circulars displaying hours, minutes + * and seconds. It additionally offers three freely formatable labels. + * + * @class TK.Clock + * + * @extends TK.Widget + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {Integer} [options.thickness=10] - Thickness of the rings in percent of the maximum dimension. + * @property {Integer} [options.margin=0] - Margin between the {@link TK.Circular} in percent of the maximum dimension. + * @property {Integer} [options.size=200] - Width and height of the widget. + * @property {Boolean} [options.show_seconds=true] - Show seconds ring. + * @property {Boolean} [options.show_minutes=true] - Show minutes ring. + * @property {Boolean} [options.show_hours=true] - Show hours ring. + * @property {Integer} [options.timeout=1000] - The timeout of the redraw trigger. + * @property {Integer} [options.timeadd=10] - Set additional milliseconds to add to the timeout target system clock regulary. + * @property {Integer} [options.offset=0] - If a timeout is set offset the system time in milliseconds. + * @property {Integer} [options.fps=25] - Framerate for calculating SMTP frames + * @property {Array} [options.months=["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]] - Array containing all months names. + * @property {Array} [options.days=["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]] - Array containing all days names. + * @property {Function} [options.label=function (_date, year, month, date, day, hour, minute, second, millisecond, frame, months, days) { return ((hour < 10) ? ("0" + hour) : hour) + ":" + ((minute < 10) ? ("0" + minute) : minute) + ":" + ((second < 10) ? ("0" + second) : second);] - Callback to format the main label. + * @property {Function} [options.label_upper=function (_date, year, month, date, day, hour, minute, second, millisecond, frame, months, days) { return days[day]; }] - Callback to format the upper label. + * @property {Function} [options.label_lower=function (_date, year, month, date, day, hour, minute, second, millisecond, frame, months, days) { return ((date < 10) ? ("0" + date) : date) + ". " + months[month] + " " + year; }] - Callback to format the lower label. + * @property {Number} [options.label_scale=0.33] - The scale of `label_upper` and `label_lower` compared to the main label. + * @property {Number|String|Date} [options.time] - Set a specific time and date. To avoid auto-udates, set `timeout` to 0. + * For more information about the value, please refer to W3Schools. + */ + _class: "Clock", + Extends: TK.Widget, + _options: Object.assign(Object.create(TK.Widget.prototype._options), { + thickness: "number", + margin: "number", + size: "number", + show_seconds: "boolean", + show_minutes: "boolean", + show_hours: "boolean", + timeout: "int", + timeadd: "int", + offset: "int", + fps: "number", + months: "array", + days: "array", + label: "function", + label_upper: "function", + label_lower: "function", + label_scale: "number", + time: "object|string|number", + }), + options: { + thickness: 10, // thickness of the rings + margin: 0, // margin between the circulars + size: 200, // diameter of the whole clock + show_seconds: true, // show the seconds ring + show_minutes: true, // show the minutes ring + show_hours: true, // show the hours ring + timeout: 1000, // set a timeout to update the clock with the + // system clock regulary + timeadd: 10, // set additional milliseconds for the + // timeout target + // system clock regulary + offset: 0, // if a timeout is set offset the system time + // in milliseconds + fps: 25, // framerate for calculatind SMTP frames + months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], + days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], + label: function (_date, year, month, date, day, hour, minute, second, millisecond, frame, months, days) { + return ((hour < 10) ? ("0" + hour) : hour) + ":" + + ((minute < 10) ? ("0" + minute) : minute) + ":" + + ((second < 10) ? ("0" + second) : second); + }, + label_upper: function (_date, year, month, date, day, hour, minute, second, millisecond, frame, months, days) { + return days[day]; + }, + label_lower: function (_date, year, month, date, day, hour, minute, second, millisecond, frame, months, days) { + return ((date < 10) ? ("0" + date) : date) + ". " + months[month] + " " + year; + }, + label_scale: 0.33 // the scale of the upper and lower labels + // compared to the main label + }, + static_events: { + hide: onhide, + show: timeout, + timeout: timeout, + }, + initialize: function (options) { + var E, S; + /** + * @member {Object} TK.Clock#circulars - An object holding all three TK.Circular as members seconds, minutes and hours. + */ + this.circulars = {}; + this._margin = -1; + TK.Widget.prototype.initialize.call(this, options); + this.set("time", this.options.time); + + /** + * @member {HTMLDivElement} TK.Clock#element - The main DIV element. Has class toolkit-clock + */ + if (!(E = this.element)) this.element = E = TK.element("div"); + /** + * @member {SVGImage} TK.Clock#svg - The main SVG image. + */ + this.svg = S = TK.make_svg("svg"); + this.widgetize(E, true, true, true); + TK.add_class(E, "toolkit-clock"); + + /** + * @member {SVGText} TK.Clock#_label - The center label showing the time. Has classtoolkit-label + */ + this._label = TK.make_svg("text", { + "class": "toolkit-label", + "text-anchor": "middle", + "style": "dominant-baseline: central;" + }); + /** + * @member {SVGText} TK.Clock#_label_upper - The upper label showing the day. Has classtoolkit-label-upper + */ + this._label_upper = TK.make_svg("text", { + "class": "toolkit-label-upper", + "text-anchor": "middle", + "style": "dominant-baseline: central;" + }); + /** @member {SVGText} TK.Clock#_label_lower - The lower label showing the date. Has classtoolkit-label-lower + */ + this._label_lower = TK.make_svg("text", { + "class": "toolkit-label-lower", + "text-anchor": "middle", + "style": "dominant-baseline: central;" + }); + S.appendChild(this._label); + S.appendChild(this._label_upper); + S.appendChild(this._label_lower); + E.appendChild(S); + + var circ_options = { + container: S, + show_hand: false, + start: 270, + angle: 360, + min: 0 + }; + + /** + * @member {Object} TK.Clock#circulars - An object containing the {@link TK.Circular} + * widgets. Members are `seconds`, `minutes` and `hours`. + */ + this.circulars.seconds = new TK.Circular(Object.assign({}, circ_options, + {max: 60, "class": "toolkit-seconds"})); + this.circulars.minutes = new TK.Circular(Object.assign({}, circ_options, + {max: 60, "class": "toolkit-minutes"})); + this.circulars.hours = new TK.Circular(Object.assign({}, circ_options, + {max: 12, "class": "toolkit-hours"})); + + this.add_child(this.circulars.seconds); + this.add_child(this.circulars.minutes); + this.add_child(this.circulars.hours); + + // start the clock + this.__timeout = timeout.bind(this); + }, + + redraw: function () { + var I = this.invalid, O = this.options; + + TK.Widget.prototype.redraw.call(this); + + if (I.size) { + this.svg.setAttribute("viewBox", format_viewbox(O.size, O.size)); + } + + if (I.validate("show_hours", "show_minutes", "show_seconds", "thickness", "margin") || I.size) { + var margin = 0; + for (var i in this.circulars) { + var circ = this.circulars[i]; + if (O["show_" + i]) { + circ.set("thickness", O.thickness); + circ.set("show_base", true); + circ.set("show_value", true); + circ.set("size", O.size); + circ.set("margin", margin); + margin += O.thickness; + margin += circ._get_stroke(); + margin += O.margin; + } else { + circ.set("show_base", false); + circ.set("show_value", false); + } + } + if(this._margin < 0) { + this._margin = margin; + } else { + this._margin = margin; + } + // force set_labels + I.label = true; + } + + if (I.validate("label", "months", "days", "size", "label_scale")) { + set_labels.call(this); + } + + if (I.validate("time", "label", "label_upper", "label_lower", "label_scale")) { + draw_time.call(this, false); + } + }, + + destroy: function () { + this._label.remove(); + this._label_upper.remove(); + this._label_lower.remove(); + this.circulars.seconds.destroy(); + this.circulars.minutes.destroy(); + this.circulars.hours.destroy(); + if (this.__to) + window.clearTimeout(this.__to); + TK.Widget.prototype.destroy.call(this); + }, + + set: function (key, value) { + switch (key) { + case "time": + if (Object.prototype.toString.call(value) === '[object Date]') + break; + value = new Date(value); + break; + } + return TK.Widget.prototype.set.call(this, key, value); + }, +}); +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/colorpicker.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/colorpicker.js new file mode 100644 index 0000000000..e29afa0c69 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/colorpicker.js @@ -0,0 +1,565 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function (w, TK) { + +var color_options = [ "rgb", "hsl", "hex", "hue", "saturation", "lightness", "red", "green", "blue" ]; + +var checkinput = function (e) { + var I = this.hex._input; + if (e.keyCode && e.keyCode == 13) { + apply.call(this); + return; + } + if (e.keyCode && e.keyCode == 27) { + cancel.call(this); + return; + } + if (I.value.substr(0, 1) == "#") + I.value = I.value.substr(1); + if (e.type == "paste" && I.value.length == 3) { + I.value = I.value[0] + I.value[0] + + I.value[1] + I.value[1] + + I.value[2] + I.value[2]; + } + if (I.value.length == 6) { + this.set("hex", I.value); + } +} +var cancel = function () { + /** + * Is fired whenever the cancel button gets clicked or ESC is hit on input. + * + * @event TK.ColorPicker#cancel + */ + fevent.call(this, "cancel"); +} +var apply = function () { + /** + * Is fired whenever the apply button gets clicked or return is hit on input. + * + * @event TK.ColorPicker#apply + * @param {object} colors - Object containing all color objects: `rgb`, `hsl`, `hex`, `hue`, `saturation`, `lightness`, `red`, `green`, `blue` + */ + fevent.call(this, "apply", true); +} + +var fevent = function (e, useraction) { + var O = this.options; + if (useraction) { + this.fire_event("userset", "rgb", O.rgb); + this.fire_event("userset", "hsl", O.hsl); + this.fire_event("userset", "hex", O.hex); + this.fire_event("userset", "hue", O.hue); + this.fire_event("userset", "saturation", O.saturation); + this.fire_event("userset", "lightness", O.lightness); + this.fire_event("userset", "red", O.red); + this.fire_event("userset", "green", O.green); + this.fire_event("userset", "blue", O.blue); + } + this.fire_event(e, { + rgb: O.rgb, + hsl: O.hsl, + hex: O.hex, + hue: O.hue, + saturation: O.saturation, + lightness: O.lightness, + red: O.red, + green: O.green, + blue: O.blue, + }); +} + +var color_atoms = { "hue":"hsl", "saturation":"hsl", "lightness":"hsl", "red":"rgb", "green":"rgb", "blue":"rgb" }; + +function set_atoms (key, value) { + var O = this.options; + var atoms = Object.keys(color_atoms); + for ( var i = 0; i < atoms.length; i++) { + var atom = atoms[i]; + if (key !== atom) { + O[atom] = O[color_atoms[atom]][atom.substr(0,1)] + if (this[atom]) + this[atom].set("value", O[atom]); + } + } + if (key !== "hex") + O.hex = this.rgb2hex(O.rgb); +} + + +/** + * TK.ColorPicker provides a collection of widgets to select a color in + * RGB or HSL color space. + * + * @class TK.ColorPicker + * + * @extends TK.Container + * + * @implements TK.Colors + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {object} [hsl={h:0, s:0.5, l:0}] - An object containing members `h`ue, `s`aturation and `l`ightness as numerical values. + * @property {object} [rgb={r:0, r:0, b:0}] - An object containing members `r`ed, `g`reen and `b`lue as numerical values. + * @property {string} [hex=000000] - A HEX color value, either with or without leading `#`. + * @property {number} [hue=0] - A numerical value 0..1 for the hue. + * @property {number} [saturation=0] - A numerical value 0..1 for the saturation. + * @property {number} [lightness=0] - A numerical value 0..1 for the lightness. + * @property {number} [red=0] - A numerical value 0..255 for the amount of red. + * @property {number} [green=0] - A numerical value 0..255 for the amount of green. + * @property {number} [blue=0] - A numerical value 0..255 for the amount of blue. + * @property {boolean} [show_hue=true] - Set to `false` to hide the {@link TK.ValueKnob} for hue. + * @property {boolean} [show_saturation=true] - Set to `false` to hide the {@link TK.ValueKnob} for saturation. + * @property {boolean} [show_lightness=true] - Set to `false` to hide the {@link TK.ValueKnob} for lightness. + * @property {boolean} [show_red=true] - Set to `false` to hide the {@link TK.ValueKnob} for red. + * @property {boolean} [show_green=true] - Set to `false` to hide the {@link TK.ValueKnob} for green. + * @property {boolean} [show_blue=true] - Set to `false` to hide the {@link TK.ValueKnob} for blue. + * @property {boolean} [show_hex=true] - Set to `false` to hide the {@link TK.Value} for the HEX color. + * @property {boolean} [show_apply=true] - Set to `false` to hide the {@link TK.Button} to apply. + * @property {boolean} [show_cancel=true] - Set to `false` to hide the {@link TK.Button} to cancel. + * @property {boolean} [show_canvas=true] - Set to `false` to hide the color canvas. + * @property {boolean} [show_grayscale=true] - Set to `false` to hide the grayscale. + * @property {boolean} [show_indicator=true] - Set to `false` to hide the color indicator. + */ + + +TK.ColorPicker = TK.class({ + + _class: "ColorPicker", + Extends: TK.Container, + Implements: [TK.Colors], + + _options: Object.assign(Object.create(TK.Container.prototype._options), { + hsl: "object", + rgb: "object", + hex: "string", + hue: "number", + saturation: "number", + lightness: "number", + red: "number", + green: "number", + blue: "number", + }), + options: { + hsl: {h:0, s:0.5, l:0}, + rgb: {r:0, g:0, b:0}, + hex: "000000", + hue: 0, + saturation: 0.5, + lightness: 0, + red: 0, + green: 0, + blue: 0, + }, + initialize: function (options) { + TK.Container.prototype.initialize.call(this, options); + var E = this.element; + /** @member {HTMLDivElement} TK.ColorPicker#element - The main DIV container. + * Has class toolkit-color-picker. + */ + TK.add_class(E, "toolkit-color-picker"); + + /** + * @member {TK.Range} TK.ColorPicker#range_x - The {@link TK.Range} for the x axis. + */ + this.range_x = new TK.Range({ + min: 0, + max: 1, + }); + + /** + * @member {TK.Range} TK.ColorPicker#range_y - The {@link TK.Range} for the y axis. + */ + this.range_y = new TK.Range({ + min: 0, + max: 1, + reverse: true, + }); + + /** + * @member {TK.Range} TK.ColorPicker#drag_x - The {@link TK.DragValue} for the x axis. + */ + this.drag_x = new TK.DragValue(this, { + range: (function () { return this.range_x; }).bind(this), + get: function () { return this.parent.options.hue; }, + set: function (v) { this.parent.userset("hue", this.parent.range_x.snap(v)); }, + direction: "horizontal", + onstartcapture: function (e) { + if (e.start.target.classList.contains("toolkit-indicator")) return; + var ev = e.stouch ? e.stouch : e.start; + var x = ev.clientX - this.parent._canvas.getBoundingClientRect().left; + this.parent.set("hue", this.options.range().px2val(x)); + } + }); + /** + * @member {TK.Range} TK.ColorPicker#drag_y - The {@link TK.DragValue} for the y axis. + */ + this.drag_y = new TK.DragValue(this, { + range: (function () { return this.range_y; }).bind(this), + get: function () { return this.parent.options.lightness; }, + set: function (v) { this.parent.userset("lightness", this.parent.range_y.snap(v)); }, + direction: "vertical", + onstartcapture: function (e) { + if (e.start.target.classList.contains("toolkit-indicator")) return; + var ev = e.stouch ? e.stouch : e.start; + var y = ev.clientY - this.parent._canvas.getBoundingClientRect().top; + this.parent.set("lightness", 1 - this.options.range().px2val(y)); + } + }); + + if (options.rgb) + this.set("rgb", options.rgb); + if (options.hex) + this.set("hex", options.hex); + if (options.hsl) + this.set("hsl", options.hsl); + }, + resize: function () { + var rect = this._canvas.getBoundingClientRect(); + this.range_x.set("basis", rect.width); + this.range_y.set("basis", rect.height); + }, + redraw: function () { + TK.Container.prototype.redraw.call(this); + var I = this.invalid; + var O = this.options; + var E = this.element; + if (I.validate("rgb", "hsl", "hex", "hue", "saturation", "lightness", "red", "green", "blue")) { + var bw = this.rgb2bw(O.rgb); + var bg = "rgb("+parseInt(O.red)+","+parseInt(O.green)+","+parseInt(O.blue)+")"; + this.hex._input.style.backgroundColor = bg; + this.hex._input.style.color = bw; + this.hex.set("value", O.hex); + + this._indicator.style.left = (O.hue * 100) + "%"; + this._indicator.style.top = (O.lightness * 100) + "%"; + this._indicator.style.backgroundColor = bg; + this._indicator.style.color = bw; + + this._grayscale.style.opacity = 1 - O.saturation; + } + }, + set: function (key, value) { + var O = this.options; + if (color_options.indexOf(key) > -1) { + switch (key) { + case "rgb": + O.hsl = this.rgb2hsl(value); + break; + case "hsl": + O.rgb = this.hsl2rgb(value); + break; + case "hex": + O.rgb = this.hex2rgb(value); + O.hsl = this.rgb2hsl(O.rgb); + break; + case "hue": + O.hsl = {h:Math.min(1,Math.max(0,value)), s:O.saturation, l:O.lightness}; + O.rgb = this.hsl2rgb(O.hsl); + break; + case "saturation": + O.hsl = {h:O.hue, s:Math.min(1,Math.max(0,value)), l:O.lightness}; + O.rgb = this.hsl2rgb(O.hsl); + break; + case "lightness": + O.hsl = {h:O.hue, s:O.saturation, l:Math.min(1,Math.max(0,value))}; + O.rgb = this.hsl2rgb(O.hsl); + break; + case "red": + O.rgb = {r:Math.min(255,Math.max(0,value)), g:O.green, b:O.blue}; + O.hsl = this.rgb2hsl(O.rgb); + break; + case "green": + O.rgb = {r:O.red, g:Math.min(255,Math.max(0,value)), b:O.blue}; + O.hsl = this.rgb2hsl(O.rgb); + break; + case "blue": + O.rgb = {r:O.red, g:O.green, b:Math.min(255,Math.max(0,value))}; + O.hsl = this.rgb2hsl(O.rgb); + break; + } + set_atoms.call(this, key, value); + } + return TK.Container.prototype.set.call(this, key, value); + } +}); + +/** + * @member {HTMLDivElement} TK.ColorPicker#canvas - The color background. + * Has class `toolkit-canvas`, + */ +TK.ChildElement(TK.ColorPicker, "canvas", { + show: true, + append: function () { + this.element.appendChild(this._canvas); + this.drag_x.set("node", this._canvas); + this.drag_y.set("node", this._canvas); + }, +}); +/** + * @member {HTMLDivElement} TK.ColorPicker#grayscale - The grayscale background. + * Has class `toolkit-grayscale`, + */ +TK.ChildElement(TK.ColorPicker, "grayscale", { + show: true, + append: function () { + this._canvas.appendChild(this._grayscale); + }, +}); +/** + * @member {HTMLDivElement} TK.ColorPicker#indicator - The indicator element. + * Has class `toolkit-indicator`, + */ +TK.ChildElement(TK.ColorPicker, "indicator", { + show: true, + append: function () { + this._canvas.appendChild(this._indicator); + }, +}); + +/** + * @member {TK.Value} TK.ColorPicker#hex - The {@link TK.Value} for the HEX color. + * Has class `toolkit-hex`, + */ +TK.ChildWidget(TK.ColorPicker, "hex", { + create: TK.Value, + show: true, + static_events: { + "userset": function (key, val) { + if (key == "value") this.parent.userset("hex", val); + }, + "keyup": function (e) { checkinput.call(this.parent, e); }, + "paste": function (e) { checkinput.call(this.parent, e); }, + }, + default_options: { + format: TK.FORMAT("%s"), + "class": "toolkit-hex", + set: function (v) { + var p=0, tmp; + if (v[0] == "#") + v = v.substring(1); + while (v.length < 6) { + tmp = v.slice(0, p+1); + tmp += v[p]; + tmp += v.slice(p+1); + v = tmp; + p+=2; + } + return v; + }, + size: 7, + maxlength: 7, + }, + map_options: { + "hex" : "value" + }, + inherit_options: true, +}); + +/** + * @member {TK.ValueKnob} TK.ColorPicker#hue - The {@link TK.ValueKnob} for the hue. + * Has class `toolkit-hue`, + */ +TK.ChildWidget(TK.ColorPicker, "hue", { + create: TK.ValueKnob, + option: "show_hsl", + show: true, + static_events: { + "userset": function (key, val) { + if (key == "value") this.parent.userset("hue", val); + }, + }, + default_options: { + title: "Hue", + min: 0, + max: 1, + "class": "toolkit-hue", + }, + map_options: { + "hue" : "value" + }, + inherit_options: true, + blacklist_options: ["x", "y", "value"], +}); +/** + * @member {TK.ValueKnob} TK.ColorPicker#saturation - The {@link TK.ValueKnob} for the saturation. + * Has class `toolkit-saturation`, + */ +TK.ChildWidget(TK.ColorPicker, "saturation", { + create: TK.ValueKnob, + show: true, + static_events: { + "userset": function (key, val) { + if (key == "value") this.parent.userset("saturation", val); + }, + }, + default_options: { + title: "Saturation", + min: 0, + max: 1, + "class": "toolkit-saturation", + }, + map_options: { + "saturation" : "value" + }, + inherit_options: true, + blacklist_options: ["x", "y", "value"], +}); +/** + * @member {TK.ValueKnob} TK.ColorPicker#lightness - The {@link TK.ValueKnob} for the lightness. + * Has class `toolkit-lightness`, + */ +TK.ChildWidget(TK.ColorPicker, "lightness", { + create: TK.ValueKnob, + option: "show_hsl", + show: true, + static_events: { + "userset": function (key, val) { + if (key == "value") this.parent.userset("lightness", val); + }, + }, + default_options: { + title: "Lightness", + min: 0, + max: 1, + "class": "toolkit-lightness", + }, + map_options: { + "lightness" : "value" + }, + inherit_options: true, + blacklist_options: ["x", "y", "value"], +}); +/** + * @member {TK.ValueKnob} TK.ColorPicker#red - The {@link TK.ValueKnob} for the red color. + * Has class `toolkit-red`, + */ +TK.ChildWidget(TK.ColorPicker, "red", { + create: TK.ValueKnob, + option: "show_rgb", + show: true, + static_events: { + "userset": function (key, val) { + if (key == "value") this.parent.userset("red", val); + }, + }, + default_options: { + title: "Red", + min: 0, + max: 255, + snap: 1, + value_format: function (v) { return parseInt(v); }, + set: function (v) { return Math.round(v); }, + "class": "toolkit-red", + }, + map_options: { + "red" : "value" + }, + inherit_options: true, + blacklist_options: ["x", "y", "value"], +}); +/** + * @member {TK.ValueKnob} TK.ColorPicker#green - The {@link TK.ValueKnob} for the green color. + * Has class `toolkit-green`, + */ +TK.ChildWidget(TK.ColorPicker, "green", { + create: TK.ValueKnob, + option: "show_rgb", + show: true, + static_events: { + "userset": function (key, val) { + if (key == "value") this.parent.userset("green", val); + }, + }, + default_options: { + title: "Green", + min: 0, + max: 255, + snap: 1, + value_format: function (v) { return parseInt(v); }, + set: function (v) { return Math.round(v); }, + "class": "toolkit-green", + }, + map_options: { + "green" : "value" + }, + inherit_options: true, + blacklist_options: ["x", "y", "value"], +}); +/** + * @member {TK.ValueKnob} TK.ColorPicker#blue - The {@link TK.ValueKnob} for the blue color. + * Has class `toolkit-blue`, + */ +TK.ChildWidget(TK.ColorPicker, "blue", { + create: TK.ValueKnob, + option: "show_rgb", + show: true, + static_events: { + "userset": function (key, val) { + if (key == "value") this.parent.userset("blue", val); + }, + }, + default_options: { + title: "Blue", + min: 0, + max: 255, + snap: 1, + value_format: function (v) { return parseInt(v); }, + set: function (v) { return Math.round(v); }, + "class": "toolkit-blue", + }, + map_options: { + "blue" : "value" + }, + inherit_options: true, + blacklist_options: ["x", "y", "value"], +}); +/** + * @member {TK.Button} TK.ColorPicker#apply - The {@link TK.Button} to apply. + * Has class `toolkit-apply`, + */ +TK.ChildWidget(TK.ColorPicker, "apply", { + create: TK.Button, + show: true, + static_events: { + "click": function () { apply.call(this.parent); }, + }, + default_options: { + "label" : "Apply", + "class": "toolkit-apply", + }, +}); +/** + * @member {TK.Button} TK.ColorPicker#cancel - The {@link TK.Button} to cancel. + * Has class `toolkit-cancel`, + */ +TK.ChildWidget(TK.ColorPicker, "cancel", { + create: TK.Button, + show: true, + static_events: { + "click": function () { cancel.call(this.parent); }, + }, + default_options: { + "label" : "Cancel", + "class" : "toolkit-cancel", + }, +}); + +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/colorpickerdialog.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/colorpickerdialog.js new file mode 100644 index 0000000000..d4edddb7c5 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/colorpickerdialog.js @@ -0,0 +1,75 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function (w, TK) { + +function cancel () { + var self = this.parent; + self.fire_event.call(self, "cancel"); + self.close(); +} + +function apply (color) { + var self = this.parent; + self.fire_event.call(self, "apply", color); + self.close(); +} + +/** + * A {@link TK.Dialog} window containing a {@link TK.ColorPicker}. It can be opened + * programatically and closes automatically on the appropriate user + * interactions like hitting ESC or clicking `apply`. TK.ColorPickerDialog + * inherits all options of TK.ColorPicker. + * + * @class TK.ColorPickerDialog + * + * @extends TK.Dialog + * + * @param {Object} [options={ }] options - An object containing initial options. + * + */ +TK.ColorPickerDialog = TK.class({ + + _class: "ColorPickerDialog", + Extends: TK.Dialog, + + initialize: function (options) { + TK.Dialog.prototype.initialize.call(this, options); + /** @member {HTMLDivElement} TK.ColorPickerDialog#element - The main DIV container. + * Has class toolkit-color-picker-dialog. + */ + TK.add_class(this.element, "toolkit-color-picker-dialog"); + }, +}); + +/** + * @member {TK.ColorPicker} TK.ColorPickerDialog#colorpicker - The {@ink TK.ColorPicker} widget. + */ +TK.ChildWidget(TK.ColorPickerDialog, "colorpicker", { + create: TK.ColorPicker, + show: true, + inherit_options: true, + userset_delegate: true, + static_events: { + cancel: cancel, + apply: apply, + }, +}); + +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/colors.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/colors.js new file mode 100644 index 0000000000..9f83be8f1a --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/colors.js @@ -0,0 +1,819 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function(w, TK){ + +/* helpers */ + +var pad = function (n, width, z) { + z = z || '0'; + n = n + ''; + return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n; +} + +var decode_args = function () { + /* decode random arguments. Expects an arguments array + * from another function call as first argument and a + * series of member names the arguments should be decoded + * to. E.g. decode_args(arguments, "r", "g", "b") + * Arguments array can consist of: + * A single array: member names are mapped to the array + * ([[50,100,150]],"r","g","b") => {"r":50,"g":100,"b":150} + * + * An object already containing the members: object is returned + * ([{"r":50,"g":100,"b":150}, ...) => {"r":50,"g":100,"b":150} + * + * Multiple values: values are mapped to member names + * ([50,100,150],"r","g","b") => {"r":50,"g":100,"b":150} + */ + var out = {}; + if (arguments[0][0] instanceof Array) { + for (var i = 0; i < arguments.length - 1; i++) + out[arguments[i+1]] = arguments[0][0][i]; + } else if (typeof(arguments[0][0]) === "object") { + out = arguments[0][0]; + } else { + for (var i = 0; i < arguments.length - 1; i++) + out[arguments[i+1]] = arguments[0][i]; + } + return out; +} + +var decode_color = function (args) { + /* detects type of input and disassembles it to a useful object. + * Only argument is an arguments array from another function. + * (["lightcoral"]) => {"type":"string","hex":"#F08080","string":"lightcoral","r":240,"g":128,"b":128} + * (["#F08080"]) => {"type":"hex","hex":"#F08080","r":240,"g":128,"b":128} + * ([[0,0.31,0.28]] => {"type":"hsl","h":0,"s":0.31,"l":0.28} + * ([240,128,128] => {"type":"rgb","r":240,"g":128,"b":128} + * ([{"r":240,"g":128,"b":128}] => {"type":"rgb","r":240,"g":128,"b":128} + */ + if (typeof args[0] === "string" && args[0][0] === "#") { + // HEX string + var res = hex2rgb(args[0]); + res["type"] = "hex"; + res["hex"] = args[0]; + return res; + } + if (typeof args[0] === "string" && color_names[args[0]]) { + // color string + var res = hex2rgb("#" + color_names[args[0]]); + res["type"] = "string"; + res["string"] = args[0]; + res["hex"] = color_names[args[0]]; + return res; + } + var S = decode_args(arguments, "a", "b", "c"); + if (S.a > 0 && S.a < 1 || S.b > 0 && S.b < 1 || S.c > 0 && S.c < 1) { + // HSL + return { "h": S.a, "s": S.b, "l": S.c, "type": "hsl" }; + } + // RGB + return { "r": S.a, "g": S.b, "b": S.c, "type": "rgb" }; +} + + +/* RGB */ + +var rgb2hex = function () { + var col = decode_args(arguments, "r", "g", "b"); + return pad(parseInt(col.r).toString(16),2) + + pad(parseInt(col.g).toString(16),2) + + pad(parseInt(col.b).toString(16),2); +} +var rgb2hsl = function () { + var col = decode_args(arguments, "r", "g", "b"); + var r = col.r, g = col.g, b = col.b; + + r /= 255, g /= 255, b /= 255; + + var max = Math.max(r, g, b), min = Math.min(r, g, b); + var h, s, l = (max + min) / 2; + + if (max == min) { + h = s = 0; // achromatic + } else { + var d = max - min; + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + + switch (max) { + case r: h = (g - b) / d + (g < b ? 6 : 0); break; + case g: h = (b - r) / d + 2; break; + case b: h = (r - g) / d + 4; break; + } + + h /= 6; + } + return { h:Math.min(1, Math.max(0, h)), + s:Math.min(1, Math.max(0, s)), + l:Math.min(1, Math.max(0, l)) }; +} +var rgb2bw = function () { + return rgb2gray.apply(null, arguments) >= 0.5 ? "#000000" : "#ffffff"; +} +var rgb2wb = function () { + return rgb2gray.apply(null, arguments) < 0.5 ? "#000000" : "#ffffff"; +} +var rgb2gray = function () { + var col = decode_args(arguments, "r", "g", "b"); + return (col.r * 0.2126 + col.g * 0.7152 + col.b * 0.0722) / 255; +} + + +/* HSL */ + +function hue2rgb(p, q, t) { + if (t < 0) t += 1; + if (t > 1) t -= 1; + if (t < 1/6) return p + (q - p) * 6 * t; + if (t < 1/2) return q; + if (t < 2/3) return p + (q - p) * (2/3 - t) * 6; + return Math.min(1, Math.max(0, p)); +} +var hsl2rgb = function () { + var col = decode_args(arguments, "h", "s", "l"); + var h = col.h, s = col.s, l = col.l; + + var r, g, b; + + if (s == 0) { + r = g = b = l; // achromatic + } else { + + var q = l < 0.5 ? l * (1 + s) : l + s - l * s; + var p = 2 * l - q; + + r = hue2rgb(p, q, h + 1/3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1/3); + } + return { r:r*255, g:g*255, b:b*255 }; +} +var hsl2hex = function () { + return rgb2hex(hsl2rgb.apply(null, arguments)); +} +var hsl2bw = function () { + return rgb2bw(hsl2rgb.apply(null, arguments)); +} +var hsl2wb = function () { + return rgb2wb(hsl2rgb.apply(null, arguments)); +} +var hsl2gray = function () { + return rgb2gray(hsl2rgb.apply(null, arguments)); +} + + +/* HEX */ + +var hex2rgb = function (hex) { + hex = hex || "000000"; + if (hex[0] == "#") + hex = hex.substr(1); + if (hex.length == 3) + return { r: parseInt("0x"+hex[0] + hex[0]), + g: parseInt("0x"+hex[1] + hex[1]), + b: parseInt("0x"+hex[2] + hex[2]) }; + return { r: parseInt("0x"+hex.substr(0,2)), + g: parseInt("0x"+hex.substr(2,2)), + b: parseInt("0x"+hex.substr(4,2)) }; +} +var hex2hsl = function (hex) { + return rgb2hsl(hex2rgb(hex)) +} +var hex2bw = function (hex) { + return rgb2bw(hex2rgb(hex)) +} +var hex2wb = function (hex) { + return rgb2wb(hex2rgb(hex)) +} +var hex2gray = function (hex) { + return rgb2gray(hex2rgb(hex)) +} + + +/* STRING */ + +var name2hex = function (name) { + return color_names[name.toLowerCase]; +} +var name2rgb = function (name) { + return hex2rgb(color_names[name.toLowerCase]); +} +var name2hsl = function (name) { + return hex2hsl(color_names[name.toLowerCase]); +} +var name2bw = function (name) { + return hex2bw(color_names[name.toLowerCase]); +} +var name2wb = function (name) { + return hex2wb(color_names[name.toLowerCase]); +} +var name2gray = function (name) { + return hex2gray(color_names[name.toLowerCase]); +} + + +/* UNIVERSAL */ + +var color2rgb = function () { + var C = decode_color(arguments); + switch (C.type) { + case "rgb": return C; + case "hex": return C; + case "hsl": return rgb2hsl(C); + case "string": return C; + } +} +var color2hsl = function () { + var C = decode_color(arguments); + switch (C.type) { + case "rgb": return rgb2hsl(C); + case "hex": return rgb2hsl(C); + case "hsl": return C; + case "string": return rgb2hsl(C); + } +} +var color2hex = function () { + var C = decode_color(arguments); + switch (C.type) { + case "rgb": return rgb2hex(C); + case "hex": return C.hex; + case "hsl": return hsl2hex(C); + case "string": return rgb2hex(C); + } +} +var color2bw = function () { + var C = decode_color(arguments); + switch (C.type) { + case "rgb": return rgb2bw(C); + case "hex": return rgb2bw(C); + case "hsl": return hsl2bw(C); + case "string": return rgb2bw(C); + } +} +var color2wb = function () { + var C = decode_color(arguments); + switch (C.type) { + case "rgb": return rgb2wb(C); + case "hex": return rgb2wb(C); + case "hsl": return hsl2wb(C); + case "string": return rgb2wb(C); + } +} +var color2gray = function () { + var C = decode_color(arguments); + switch (C.type) { + case "rgb": return rgb2bw(C); + case "hex": return rgb2bw(C); + case "hsl": return hsl2bw(C); + case "string": return rgb2bw(C); + } +} + + +/** + * TK.Colors provides a couple of functions for easy-to-use color calculations + * and conversions. Functions requiring RGB or HSL color definitions as + * argument (all `rgb2x` and `hsl2x`) can be called with different types of arguments + * to make using them more convenient. Examples: + *
  • `rgb2hsl(240, 128, 128)`
  • + *
  • rgb2hsl({'r':240,'g':128,'b':128})
  • + *
  • rgb2hsl([240, 128, 128])
+ * The universal functions `color2x` take even more diverse arguments. + * The following examples all define the same color: + *
  • ("lightcoral")
  • + *
  • ("#F08080")
  • + *
  • ([0,0.31,0.28])
  • + *
  • (240,128,128)
  • + *
  • ({"r":240,"g":128,"b":128})
+ * + * @mixin TK.Colors + */ + +TK.Colors = TK.class({ + /** + * Returns an object containing red ('r'), green ('g') and blue ('b') + * from any type of valid color. + * + * @method TK.Colors#color2rgb + * + * @param {number|array|object|string} 1st_value - red (0..255) or hue (0..1) or object with members `r, g, b` or `h, s, l` or array of RGB or HSL or color name or hex string. + * @param {number} [2nd_value] - green (0..255) or saturation (0..1) + * @param {number} [3rd_value] - blue (0..255) or lightnes (0..1) + * + * @returns {object} Object with members r, g and b as numbers (0..255) + */ + color2rgb: color2rgb, + + /** + * Returns an object containing hue ('h'), saturation ('s') and lightness ('l') + * from any type of valid color. + * + * @method TK.Colors#color2hsl + * + * @param {number|array|object|string} 1st_value - red (0..255) or hue (0..1) or object with members `r, g, b` or `h, s, l` or array of RGB or HSL or color name or hex string. + * @param {number} [2nd_value] - green (0..255) or saturation (0..1) + * @param {number} [3rd_value] - blue (0..255) or lightnes (0..1) + * + * @returns {object} Object with members h, s and l as numbers (0..1) + */ + color2hsl: color2hsl, + + /** + * Returns a hex color string + * from any type of valid color. + * + * @method TK.Colors#color2hex + * + * @param {number|array|object|string} 1st_value - red (0..255) or hue (0..1) or object with members `r, g, b` or `h, s, l` or array of RGB or HSL or color name or hex string. + * @param {number} [2nd_value] - green (0..255) or saturation (0..1) + * @param {number} [3rd_value] - blue (0..255) or lightnes (0..1) + * + * @returns {string} Hex color string. + */ + color2hex: color2hex, + + /** + * Returns a hex color string either black or white at highest contrast compared to the argument + * from any type of valid color. + * + * @method TK.Colors#color2bw + * + * @param {number|array|object|string} 1st_value - red (0..255) or hue (0..1) or object with members `r, g, b` or `h, s, l` or array of RGB or HSL or color name or hex string. + * @param {number} [2nd_value] - green (0..255) or saturation (0..1) + * @param {number} [3rd_value] - blue (0..255) or lightnes (0..1) + * + * @returns {string} Hex color string. + */ + color2bw: color2bw, + + /** + * Returns a hex color string either black or white at lowest contrast compared to the argument + * from any type of valid color. + * + * @method TK.Colors#color2wb + * + * @param {number|array|object|string} 1st_value - red (0..255) or hue (0..1) or object with members `r, g, b` or `h, s, l` or array of RGB or HSL or color name or hex string. + * @param {number} [2nd_value] - green (0..255) or saturation (0..1) + * @param {number} [3rd_value] - blue (0..255) or lightnes (0..1) + * + * @returns {string} Hex color string. + */ + color2wb: color2wb, + + /** + * Returns a hex color string of the grayscaled argument + * from any type of valid color. + * + * @method TK.Colors#color2gray + * + * @param {number|array|object|string} 1st_value - red (0..255) or hue (0..1) or object with members `r, g, b` or `h, s, l` or array of RGB or HSL or color name or hex string. + * @param {number} [2nd_value] - green (0..255) or saturation (0..1) + * @param {number} [3rd_value] - blue (0..255) or lightnes (0..1) + * + * @returns {string} Hex color string. + */ + color2gray: color2gray, + + + + /** + * Returns an object containing hue ('h'), saturation ('s') and lightness ('l') + * from a RGB color. + * + * @method TK.Colors#rgb2hsl + * + * @param {number|array|object|string} 1st_value - red (0..255) or object with members `r, g, b` or array of RGB or color name or hex string. + * @param {number} [2nd_value] - green (0..255) or saturation (0..1) + * @param {number} [3rd_value] - blue (0..255) or lightnes (0..1) + * + * @returns {object} Object with members h, s and l as numbers (0..1) + */ + rgb2hsl: rgb2hsl, + + /** + * Returns a hex color string + * from a RGB color. + * + * @method TK.Colors#rgb2hex + * + * @param {number|array|object|string} 1st_value - red (0..255) or object with members `r, g, b` or array of RGB or color name or hex string. + * @param {number} [2nd_value] - green (0..255) or saturation (0..1) + * @param {number} [3rd_value] - blue (0..255) or lightnes (0..1) + * + * @returns {string} Hex color string. + */ + rgb2hex: rgb2hex, + + /** + * Returns a hex color string either black or white at highest contrast compared to the argument + * from a RGB color. + * + * @method TK.Colors#rgb2bw + * + * @param {number|array|object|string} 1st_value - red (0..255) or object with members `r, g, b` or array of RGB or color name or hex string. + * @param {number} [2nd_value] - green (0..255) or saturation (0..1) + * @param {number} [3rd_value] - blue (0..255) or lightnes (0..1) + * + * @returns {string} Hex color string. + */ + rgb2bw: rgb2bw, + + /** + * Returns a hex color string either black or white at lowest contrast compared to the argument + * from a RGB color. + * + * @method TK.Colors#rgb2wb + * + * @param {number|array|object|string} 1st_value - red (0..255) or object with members `r, g, b` or array of RGB or color name or hex string. + * @param {number} [2nd_value] - green (0..255) or saturation (0..1) + * @param {number} [3rd_value] - blue (0..255) or lightnes (0..1) + * + * @returns {string} Hex color string. + */ + rgb2wb: rgb2wb, + + /** + * Returns a hex color string of the grayscaled argument + * from a RGB color. + * + * @method TK.Colors#rgb2gray + * + * @param {number|array|object|string} 1st_value - red (0..255) or object with members `r, g, b` or array of RGB or color name or hex string. + * @param {number} [2nd_value] - green (0..255) or saturation (0..1) + * @param {number} [3rd_value] - blue (0..255) or lightnes (0..1) + * + * @returns {string} Hex color string. + */ + rgb2gray: rgb2gray, + + + + /** + * Returns an object containing red ('r'), green ('g') and blue ('b') + * from a HSL color. + * + * @method TK.Colors#hsl2rgb + * + * @param {number|array|object} 1st_value - hue (0..1) or object with members `h, s, l` or array of HSL. + * @param {number} [2nd_value] - saturation (0..1) + * @param {number} [3rd_value] - lightness (0..1) + * + * @returns {object} Object with members r, g and b as numbers (0..255) + */ + hsl2rgb: hsl2rgb, + + /** + * Returns a hex color string + * from a HSL color. + * + * @method TK.Colors#hsl2hex + * + * @param {number|array|object} 1st_value - hue (0..1) or object with members `h, s, l` or array of HSL. + * @param {number} [2nd_value] - saturation (0..1) + * @param {number} [3rd_value] - lightness (0..1) + * + * @returns {string} Hex color string. + */ + hsl2hex: hsl2hex, + + /** + * Returns a hex color string either black or white at highest contrast compared to the argument + * from a HSL color. + * + * @method TK.Colors#hsl2bw + * + * @param {number|array|object} 1st_value - hue (0..1) or object with members `h, s, l` or array of HSL. + * @param {number} [2nd_value] - saturation (0..1) + * @param {number} [3rd_value] - lightness (0..1) + * + * @returns {string} Hex color string. + */ + hsl2bw: hsl2bw, + + /** + * Returns a hex color string either black or white at lowest contrast compared to the argument + * from a HSL color. + * + * @method TK.Colors#hsl2wb + * + * @param {number|array|object} 1st_value - hue (0..1) or object with members `h, s, l` or array of HSL. + * @param {number} [2nd_value] - saturation (0..1) + * @param {number} [3rd_value] - lightness (0..1) + * + * @returns {string} Hex color string. + */ + hsl2wb: hsl2wb, + + /** + * Returns a hex color string of the grayscaled argument + * from a HSL color. + * + * @method TK.Colors#hsl2gray + * + * @param {number|array|object} 1st_value - hue (0..1) or object with members `h, s, l` or array of HSL. + * @param {number} [2nd_value] - saturation (0..1) + * @param {number} [3rd_value] - lightness (0..1) + * + * @returns {string} Hex color string. + */ + hsl2gray: hsl2gray, + + + + /** + * Returns an object containing red ('r'), green ('g') and blue ('b') + * from a hex color string. + * + * @method TK.Colors#hex2rgb + * + * @param {string} hex - Hex color string. + * + * @returns {object} Object with members r, g and b as numbers (0..255) + */ + hex2rgb: hex2rgb, + + /** + * Returns an object containing hue ('h'), saturation ('s') and lightness ('l') + * from a hex color string. + * + * @method TK.Colors#hex2hsl + * + * @param {string} hex - Hex color string. + * + * @returns {object} Object with members h, s and l as numbers (0..1) + */ + hex2hsl: hex2hsl, + + /** + * Returns a hex color string either black or white at highest contrast compared to the argument + * from a hex color string. + * + * @method TK.Colors#hex2bw + * + * @param {string} hex - Hex color string. + * + * @returns {string} Hex color string. + */ + hex2bw: hex2bw, + + /** + * Returns a hex color string either black or white at lowest contrast compared to the argument + * from a hex color string. + * + * @method TK.Colors#hex2wb + * + * @param {string} hex - Hex color string. + * + * @returns {string} Hex color string. + */ + hex2wb: hex2wb, + + /** + * Returns a hex color string of the grayscaled argument + * from a hex color string. + * + * @method TK.Colors#hex2gray + * + * @param {string} hex - Hex color string. + * + * @returns {string} Hex color string. + */ + hex2gray: hex2gray, + + + + /** + * Returns an object containing red ('r'), green ('g') and blue ('b') + * from a color name. + * + * @method TK.Colors#name2rgb + * + * @param {string} color - Color name. + * + * @returns {object} Object with members r, g and b as numbers (0..255) + */ + name2rgb: name2rgb, + + /** + * Returns an object containing hue ('h'), saturation ('s') and lightness ('l') + * from a color name. + * + * @method TK.Colors#name2hsl + * + * @param {string} color - Color name. + * + * @returns {object} Object with members h, s and l as numbers (0..1) + */ + name2hsl: name2hsl, + + /** + * Returns a hex color string + * from a color name. + * + * @method TK.Colors#name2hex + * + * @param {string} color - Color name. + * + * @returns {string} Hex color string. + */ + name2hex: name2hex, + + /** + * Returns a hex color string either black or white at highest contrast compared to the argument + * from a color name. + * + * @method TK.Colors#name2bw + * + * @param {string} color - Color name. + * + * @returns {string} Hex color string. + */ + name2bw: name2bw, + + /** + * Returns a hex color string either black or white at lowest contrast compared to the argument + * from a color name. + * + * @method TK.Colors#name2wb + * + * @param {string} color - Color name. + * + * @returns {string} Hex color string. + */ + name2wb: name2wb, + + /** + * Returns a hex color string of the grayscaled argument + * from a color name. + * + * @method TK.Colors#name2gray + * + * @param {string} color - Color name. + * + * @returns {string} Hex color string. + */ + name2gray: name2gray, + +}); + +var color_names = { + "lightcoral" : "f08080", + "salmon" : "fa8072", + "darksalmon" : "e9967a", + "lightsalmon" : "ffa07a", + "crimson" : "dc143c", + "red" : "ff0000", + "firebrick" : "b22222", + "darkred" : "8b0000", + "pink" : "ffc0cb", + "lightpink" : "ffb6c1", + "hotpink" : "ff69b4", + "deeppink" : "ff1493", + "mediumvioletred" : "c71585", + "palevioletred" : "db7093", + "coral" : "ff7f50", + "tomato" : "ff6347", + "orangered" : "ff4500", + "darkorange" : "ff8c00", + "orange" : "ffa500", + "gold" : "ffd700", + "yellow" : "ffff00", + "lightyellow" : "ffffe0", + "lemonchiffon" : "fffacd", + "lightgoldenrodyellow" : "fafad2", + "papayawhip" : "ffefd5", + "moccasin" : "ffe4b5", + "peachpuff" : "ffdab9", + "palegoldenrod" : "eee8aa", + "khaki" : "f0e68c", + "darkkhaki" : "bdb76b", + "lavender" : "e6e6fa", + "thistle" : "d8bfd8", + "plum" : "dda0dd", + "violet" : "ee82ee", + "orchid" : "da70d6", + "fuchsia" : "ff00ff", + "magenta" : "ff00ff", + "mediumorchid" : "ba55d3", + "mediumpurple" : "9370db", + "amethyst" : "9966cc", + "blueviolet" : "8a2be2", + "darkviolet" : "9400d3", + "darkorchid" : "9932cc", + "darkmagenta" : "8b008b", + "purple" : "800080", + "indigo" : "4b0082", + "slateblue" : "6a5acd", + "darkslateblue" : "483d8b", + "mediumslateblue" : "7b68ee", + "greenyellow" : "adff2f", + "chartreuse" : "7fff00", + "lawngreen" : "7cfc00", + "lime" : "00ff00", + "limegreen" : "32cd32", + "palegreen" : "98fb98", + "lightgreen" : "90ee90", + "mediumspringgreen" : "00fa9a", + "springgreen" : "00ff7f", + "mediumseagreen" : "3cb371", + "seagreen" : "2e8b57", + "forestgreen" : "228b22", + "green" : "008000", + "darkgreen" : "006400", + "yellowgreen" : "9acd32", + "olivedrab" : "6b8e23", + "olive" : "808000", + "darkolivegreen" : "556b2f", + "mediumaquamarine" : "66cdaa", + "darkseagreen" : "8fbc8f", + "lightseagreen" : "20b2aa", + "darkcyan" : "008b8b", + "teal" : "008080", + "aqua" : "00ffff", + "cyan" : "00ffff", + "lightcyan" : "e0ffff", + "paleturquoise" : "afeeee", + "aquamarine" : "7fffd4", + "turquoise" : "40e0d0", + "mediumturquoise" : "48d1cc", + "darkturquoise" : "00ced1", + "cadetblue" : "5f9ea0", + "steelblue" : "4682b4", + "lightsteelblue" : "b0c4de", + "powderblue" : "b0e0e6", + "lightblue" : "add8e6", + "skyblue" : "87ceeb", + "lightskyblue" : "87cefa", + "deepskyblue" : "00bfff", + "dodgerblue" : "1e90ff", + "cornflowerblue" : "6495ed", + "royalblue" : "4169e1", + "blue" : "0000ff", + "mediumblue" : "0000cd", + "darkblue" : "00008b", + "navy" : "000080", + "midnightblue" : "191970", + "cornsilk" : "fff8dc", + "blanchedalmond" : "ffebcd", + "bisque" : "ffe4c4", + "navajowhite" : "ffdead", + "wheat" : "f5deb3", + "burlywood" : "deb887", + "tan" : "d2b48c", + "rosybrown" : "bc8f8f", + "sandybrown" : "f4a460", + "goldenrod" : "daa520", + "darkgoldenrod" : "b8860b", + "peru" : "cd853f", + "chocolate" : "d2691e", + "saddlebrown" : "8b4513", + "sienna" : "a0522d", + "brown" : "a52a2a", + "maroon" : "800000", + "white" : "ffffff", + "snow" : "fffafa", + "honeydew" : "f0fff0", + "mintcream" : "f5fffa", + "azure" : "f0ffff", + "aliceblue" : "f0f8ff", + "ghostwhite" : "f8f8ff", + "whitesmoke" : "f5f5f5", + "seashell" : "fff5ee", + "beige" : "f5f5dc", + "oldlace" : "fdf5e6", + "floralwhite" : "fffaf0", + "ivory" : "fffff0", + "antiquewhite" : "faebd7", + "linen" : "faf0e6", + "lavenderblush" : "fff0f5", + "mistyrose" : "ffe4e1", + "gainsboro" : "dcdcdc", + "lightgrey" : "d3d3d3", + "silver" : "c0c0c0", + "darkgray" : "a9a9a9", + "gray" : "808080", + "dimgray" : "696969", + "lightslategray" : "778899", + "slategray" : "708090", + "darkslategray" : "2f4f4f", + "black" : "000000" +} + +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/confirmbutton.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/confirmbutton.js new file mode 100644 index 0000000000..d7d622dea8 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/confirmbutton.js @@ -0,0 +1,162 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +"use strict"; +(function(w, TK){ + +var reset = function (e) { + if (!this.options.state) return; + if (e) { + var t = e.target; + while (t) { + if (t == this.element) return; + t = t.parentElement; + } + } + state_reset.call(this); +} + +var state_set = function () { + var T = this.__temp; + var O = this.options; + if (O.label_confirm) { + T.label = O.label; + this.set("label", O.label_confirm); + } + + if (O.icon_confirm) { + T.icon = O.icon; + this.set("icon", O.icon_confirm); + } + + T.reset = reset.bind(this); + document.addEventListener("click", T.reset, true); + + if (O.timeout) + T.timeout = setTimeout(T.reset, O.timeout); + + T.click = Date.now(); + + this.set("state", true); +} + +var state_reset = function () { + var T = this.__temp; + if (T.label) + this.set("label", T.label); + + if (T.icon) + this.set("icon", T.icon); + + if (T.timeout >= 0) + window.clearTimeout(T.timeout); + + if (T.reset) + document.removeEventListener("click", T.reset, true); + + T.reset = null; + T.timeout = -1; + T.label = ""; + T.icon = ""; + T.click = 0; + + this.set("state", false); +} + +/** + * Is fired when the button was hit two times with at least + * interrupt milliseconds between both clicks. + * + * @event TK.ConfirmButton#confirmed + */ + +var clicked = function (e) { + var T = this.__temp; + var O = this.options; + if (!O.confirm) { + this.fire_event("confirmed"); + } else if (O.state && Date.now() > T.click + O.interrupt) { + this.fire_event("confirmed"); + state_reset.call(this); + } else if (!O.state) { + state_set.call(this); + } +} + +TK.ConfirmButton = TK.class({ + /** + * ConfirmButton is a {@link TK.Button} firing the `confirmed` event + * after it was hit a second time. While waiting for the confirmation, a + * dedicated label and icon can be displayed. The button is reset to + * default if no second click appears. A click outside of the button + * resets it, too. It gets class `toolkit-active` while waiting for + * the confirmation. + * + * @class TK.ConfirmButton + * + * @extends TK.Button + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {Boolean} [options.confirm=true] - Defines if the button acts as ConfirmButton or normal Button. + * @property {Number} [options.timeout=2000] - Defines a time in milliseconds after the button resets to defaults if no second click happens. + * @property {Number} [options.interrupt=0] - Defines a duration in milliseconds within further clicks are ignored. Set to avoid double-clicks being recognized as confirmation. + * @property {String} [options.label_confirm] - The label to be used while in active state. + * @property {String} [options.icon_confirm] - The icon to be used while in active state. + */ + _class: "ConfirmButton", + Extends: TK.Button, + + _options: Object.assign(Object.create(TK.Button.prototype._options), { + confirm: "boolean", + timeout: "number", + interrupt: "number", + label_confirm : "string", + icon_confirm: "string", + }), + options: { + confirm: true, + timeout: 2000, + interrupt: 0, + label_confirm: "", + icon_confirm: "", + }, + + initialize: function (options) { + TK.Button.prototype.initialize.call(this, options); + TK.add_class(this.element, "toolkit-confirm-button"); + this.add_event("click", clicked.bind(this)); + this.__temp = { + label: "", + icon: "", + timeout: -1, + reset: null, + click: 0, + } + }, + + set: function (key, value) { + if (key == "confirm" && value == false) { + this.set("state", false); + } + TK.Button.prototype.set.call(this, key, value); + } +}); + +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/container.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/container.js new file mode 100644 index 0000000000..476fa69694 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/container.js @@ -0,0 +1,411 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function(w, TK){ +function after_hiding() { + this.__hide_id = false; + if (this.options.display_state === "hiding") + this.set("display_state", "hide"); +} +function after_showing() { + this.__hide_id = false; + if (this.options.display_state === "showing") + this.set("display_state", "show"); +} +function enable_draw_self() { + if (this._drawn) return; + this._drawn = true; + if (this.needs_redraw) { + TK.S.add(this._redraw, 1); + } + /** + * Is fired when the container is shown. + * + * @event TK.Container#show + */ + this.fire_event("show"); +} +function enable_draw_children() { + var C = this.children; + var H = this.hidden_children; + if (C) for (var i = 0; i < C.length; i++) if (!H[i]) C[i].enable_draw(); +} +function disable_draw_self() { + if (!this._drawn) return; + this._drawn = false; + if (this.needs_redraw) { + TK.S.remove(this._redraw, 1); + TK.S.remove_next(this._redraw, 1); + } + /** + * Is fired when the container is hidden. + * + * @event TK.Container#hide + */ + this.fire_event("hide"); +} +function disable_draw_children() { + var C = this.children; + var H = this.hidden_children; + if (C) for (var i = 0; i < C.length; i++) if (!H[i]) C[i].disable_draw(); +} +TK.Container = TK.class({ + /** + * TK.Container represents a <DIV> element contining various + * other widgets or DOMNodes. + * + * Containers have four different display states: show, hide, + * showing and hiding. Each of these states has a corresponding + * CSS class called toolkit-show, toolkit-hide, toolkit-showing + * and toolkit-hiding, respectively. The display state can be controlled using + * the methods {@link TK.Container#show}, {@link TK.Container#hide} and {@link TK.Widget#toggle_hidden}. + * + * A container can keep track of the display states of its child widgets. + * The display state of a child can be changed using {@link TK.Container#hide_child}, + * {@link TK.Container#show_child} and {@link TK.Container#toggle_child}. + * + * @class TK.Container + * + * @extends TK.Widget + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {String|HTMLElement} [options.content] - The content of the container. It can either be + * a string which is interpreted as Text or a DOM node. Note that this option will remove all + * child nodes from the container element including those added via append_child. + * @property {Number} [options.hiding_duration] - The duration in ms of the hiding CSS + * transition/animation of this container. If this option is not set, the transition duration + * will be determined by the computed style, which can be rather + * expensive. Setting this option explicitly can therefore be an optimization. + * @property {Number} [options.showing_duration] - The duration in ms of the showing CSS + * transition/animation of this container. + * @property {String} [options.display_state="show"] - The current display state of this container. + * Do not modify, manually. + * @property {Array} [options.children=[]] - Add child widgets on init. Will not be maintained on runtime! Just for convenience purposes on init. + */ + _class: "Container", + Extends: TK.Widget, + _options: Object.assign(Object.create(TK.Widget.prototype._options), { + content: "string|DOMNode", + display_state: "string", + hiding_duration: "number", + showing_duration: "number", + children: "array", + }), + options: { + display_state : "show", + children: [], + }, + initialize: function (options) { + var E; + TK.Widget.prototype.initialize.call(this, options); + this.hidden_children = []; + /** + * @member {HTMLDivElement} TK.Container#element - The main DIV element. Has class toolkit-container + */ + if (!(E = this.element)) this.element = E = TK.element("div"); + TK.add_class(E, "toolkit-container"); + this.widgetize(E, true, true, true); + + this.__after_hiding = after_hiding.bind(this); + this.__after_showing = after_showing.bind(this); + this.__hide_id = false; + TK.add_class(E, "toolkit-show"); + + if (this.options.children.length) + this.append_children(this.options.children); + }, + + /** + * Calls {@link TK.Container#append_child} for an array of widgets. + * + * @method TK.Container#append_children + * + * @param {Array.} children - The child widgets to append. + */ + append_children : function (a) { + a.map(this.append_child, this); + }, + /** + * Appends child.element to the container element and + * registers child as a child widget. + * + * @method TK.Container#append_child + * + * @param {TK.Widget} child - The child widget to append. + */ + append_child : function(child) { + child.set("container", this.element); + this.add_child(child); + }, + set_parent : function(parent) { + if (parent && !(parent instanceof TK.Container)) { + TK.warn("Container %o should not be child of non-container %o", this, parent); + } + TK.Widget.prototype.set_parent.call(this, parent); + }, + add_child : function(child) { + TK.Widget.prototype.add_child.call(this, child); + var H = this.hidden_children; + if (!H) this.hidden_children = H = []; + H.push(false); + }, + remove_child : function(child) { + if (!child) return; + child.disable_draw(); + child.parent = null; + var C = this.children; + if (C === null) return; + var H = this.hidden_children; + var i = C.indexOf(child); + if (i !== -1) { + C.splice(i, 1); + H.splice(i, 1); + } + }, + enable_draw: function () { + if (this._drawn) return; + enable_draw_self.call(this); + enable_draw_children.call(this); + }, + disable_draw: function () { + if (!this._drawn) return; + disable_draw_self.call(this); + disable_draw_children.call(this); + }, + /** + * Starts the transition of the display_state to hide. + * + * @method TK.Container#hide + * + */ + hide: function () { + var O = this.options; + if (O.display_state === "hide") return; + disable_draw_children.call(this); + enable_draw_self.call(this); + if (O.display_state === "hiding") return; + this.set("display_state", "hiding"); + }, + /** + * Immediately switches the display state of this container to hide. + * Unlike {@link TK.Container#hide} this method does not perform the hiding transition + * and immediately modifies the DOM by setting the toolkit-hide class. + * + * @method TK.Container#force_hide + * + */ + force_hide: function () { + var O = this.options; + if (O.display_state === "hide") return; + this.disable_draw(); + var E = this.element; + O.display_state = "hide"; + TK.add_class(E, "toolkit-hide"); + TK.remove_class(E, "toolkit-hiding", "toolkit-showing", "toolkit-show"); + }, + /** + * Starts the transition of the display_state to show. + * + * @method TK.Container#show + * + */ + show: function() { + var O = this.options; + enable_draw_self.call(this); + if (O.display_state === "show" || O.display_state === "showing") return; + this.set("display_state", "showing"); + }, + /** + * Immediately switches the display state of this container to show. + * Unlike {@link TK.Container#hide} this method does not perform the hiding transition + * and immediately modifies the DOM by setting the toolkit-show class. + * + * @method TK.Container#force_show + * + */ + force_show: function() { + var O = this.options; + if (O.display_state === "show") return; + this.enable_draw(); + var E = this.element; + O.display_state = "show"; + TK.add_class(E, "toolkit-show"); + TK.remove_class(E, "toolkit-hiding", "toolkit-showing", "toolkit-hide"); + }, + show_nodraw: function() { + var O = this.options; + if (O.display_state === "show") return; + this.set("display_state", "show"); + + var C = this.children; + var H = this.hidden_children; + if (C) for (var i = 0; i < C.length; i++) if (!H[i]) C[i].show_nodraw(); + }, + hide_nodraw: function() { + var O = this.options; + if (O.display_state === "hide") return; + this.set("display_state", "hide"); + + var C = this.children; + var H = this.hidden_children; + if (C) for (i = 0; i < C.length; i++) if (!H[i]) C[i].hide_nodraw(); + }, + + /** + * Switches the hidden state of a child to hidden. + * The argument is either the child index or the child itself. + * + * @method TK.Container#hide_child + * @param {Object|integer} child - Child or its index. + * + */ + hide_child: function(i) { + var C = this.children; + var H = this.hidden_children; + + if (typeof i !== "number") { + i = C.indexOf(i); + if (i === -1) throw("Cannot find child."); + } + + H[i] = true; + C[i].hide(); + }, + + /** + * Switches the hidden state of a child to shown. + * The argument is either the child index or the child itself. + * + * @method TK.Container#show_child + * @param {Object|integer} child - Child or its index. + * + */ + show_child: function(i) { + var C = this.children; + var H = this.hidden_children; + + if (typeof i !== "number") { + i = C.indexOf(i); + if (i === -1) throw("Cannot find child."); + } + + if (H[i]) { + H[i] = false; + if (this.is_drawn()) C[i].show(); + else C[i].show_nodraw(); + } + }, + + /** + * Toggles the hidden state of a child. + * The argument is either the child index or the child itself. + * + * @method TK.Container#toggle_child + * @param {Object|integer} child - Child or its index. + * + */ + toggle_child: function(i) { + var C = this.children; + var H = this.hidden_children; + + if (typeof i !== "number") { + i = C.indexOf(i); + if (i === -1) throw("Cannot find child."); + } + if (H[i]) this.show_child(i); + else this.hide_child(i); + }, + + visible_children: function(a) { + if (!a) a = []; + var C = this.children; + var H = this.hidden_children; + if (C) for (var i = 0; i < C.length; i++) { + if (H[i]) continue; + a.push(C[i]); + C[i].visible_children(a); + } + return a; + }, + + hidden: function() { + var state = this.options.display_state; + return TK.Widget.prototype.hidden.call(this) || state === "hiding" || state === "hide"; + }, + + redraw: function() { + var O = this.options; + var I = this.invalid; + var E = this.element; + + TK.Widget.prototype.redraw.call(this); + + if (I.display_state) { + I.display_state = false; + var time; + TK.remove_class(E, "toolkit-hiding", "toolkit-hide", "toolkit-showing", "toolkit-show"); + + if (this.__hide_id) { + window.clearTimeout(this.__hide_id); + this.__hide_id = false; + } + + switch (O.display_state) { + case "hiding": + TK.add_class(E, "toolkit-hiding"); + time = O.hiding_duration || TK.get_duration(E); + if (time > 0) { + this.__hide_id = window.setTimeout(this.__after_hiding, time); + break; + } + this.set("display_state", "hide"); + TK.remove_class(E, "toolkit-hiding"); + /* FALL THROUGH */ + case "hide": + TK.add_class(E, "toolkit-hide"); + disable_draw_self.call(this); + break; + case "showing": + TK.add_class(E, "toolkit-showing"); + time = O.showing_duration || TK.get_duration(E); + if (time > 0) { + this.__hide_id = window.setTimeout(this.__after_showing, time); + enable_draw_children.call(this); + break; + } + this.set("display_state", "show"); + TK.remove_class(E, "toolkit-showing"); + /* FALL THROUGH */ + case "show": + TK.add_class(E, "toolkit-show"); + enable_draw_children.call(this); + break; + } + } + + if (I.content) { + I.content = false; + TK.empty(E); + if (typeof O.content === "string") TK.set_content(E, O.content); + else E.appendChild(O.content); + } + }, +}); +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/crossover.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/crossover.js new file mode 100644 index 0000000000..ab2371a2ab --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/crossover.js @@ -0,0 +1,209 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +"use strict"; +(function(w, TK){ + +function invalidate_bands() { + this.invalid.bands = true; + this.trigger_draw(); +} + +function sort_bands() { + this.bands.sort(function (a, b) { + return a.options.freq - b.options.freq; + }); +} + +function limit_bands () { + if (this.options.leap) return; + sort_bands.call(this); + for (var i = 0; i < this.bands.length; i++) + _set_freq.call(this, i, this.bands[i]); +} + +function set_freq (band) { + if (this.options.leap) return; + var i = this.bands.indexOf(band); + if (i < 0) { + TK.error("Band no member of crossover"); + return; + } + _set_freq.call(this, i, band); +} + +function _set_freq (i, band) { + var freq = band.options.freq; + var dist = freq * this.get("distance") + if (i) + this.bands[i-1].set("x_max", freq - dist); + if (i < this.bands.length-1) + this.bands[i+1].set("x_min", freq + dist); +} + +TK.Crossover = TK.class({ + /** + * TK.Crossover is a {@link TK.Equalizer} displaying the response + * of a multi-band crossover filter. TK.Crossover uses {@link TK.CrossoverBand} + * as response handles. + * + * @class TK.Crossover + * + * @extends TK.Equalizer + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {Boolean} [options.leap=true] - Define if bands are allowed to leap over each other. + * @property {Number} [options.distance=0] - Set a minimal distance between bands if leaping is not allowed. + * Value is a factor of x. Example: if distance=0.2 a band cannot be moved beyond 800Hz if the upper next + * band is at 1kHz. + */ + _class: "Crossover", + Extends: TK.Equalizer, + _options: Object.assign(Object.create(TK.Equalizer.prototype._options), { + leap: "boolean", + distance: "number", + }), + options: { + range_y: {min:-60, max: 12, scale: "linear"}, + leap: true, + distance: 0, + }, + initialize: function (options) { + TK.Equalizer.prototype.initialize.call(this, options); + /** + * @member {HTMLDivElement} TK.Equalizer#element - The main DIV container. + * Has class toolkit-response-handler. + */ + TK.add_class(this.element, "toolkit-crossover"); + }, + resize: function () { + invalidate_bands.call(this); + TK.Equalizer.prototype.resize.call(this); + }, + redraw: function () { + var I = this.invalid; + var O = this.options; + var lastb = this.bands.length - 1; + var lastg = this.graphs.length - 1; + if (I.validate("bands", "accuracy")) { + I.bands = false; + I.accuracy = false; + sort_bands.call(this); + for (var i = 0; i < this.bands.length; i++) { + var f = [this.bands[i].lower.get_freq2gain()]; + if (i) + f.push(this.bands[i-1].upper.get_freq2gain()); + this._draw_graph(this.graphs[i], f); + } + this._draw_graph(this.graphs[lastg], [this.bands[lastb].upper.get_freq2gain()]); + } + TK.Equalizer.prototype.redraw.call(this); + }, + add_band: function (options, type) { + type = type || TK.CrossoverBand; + this.add_graph(); + var r = TK.Equalizer.prototype.add_band.call(this, options, type); + var that = this; + r.add_event("set_freq", function (f) { + set_freq.call(that, this); + }); + limit_bands.call(this); + return r; + }, + remove_band: function (band) { + this.remove_graph(this.graphs[this.graphs.length-1]); + var r = TK.Equalizer.prototype.remove_band.call(this, options); + limit_bands.call(this); + return r; + }, +}); + +TK.CrossoverBand = TK.class({ + /** + * TK.CrossoverBand is a {@link TK.EqBand} with an additional filter. + * + * @param {Object} [options={ }] - An object containing additional options. + * + * @property {String|Function} [lower="lowpass3"] - The type of filter for the range below cutoff frequency. See {@link TK.EqBand} for more information. + * @property {String|Function} [upper="highpass3"] - The type of filter for the range above cutoff frequency. See {@link TK.EqBand} for more information. + * @property {Function} [label=function (t, x, y, z) { return TK.sprintf("%.2f Hz", x); }] - The function formatting the handles label. + * + * @class TK.CrossoverBand + * + * @extends TK.EqBand + */ + _class: "CrossoverBand", + Extends: TK.EqBand, + _options: Object.assign(Object.create(TK.EqBand.prototype._options), { + lower: "string", + upper: "string", + }), + options: { + lower: "lowpass3", + upper: "highpass3", + label: function (t, x, y, z) { return TK.sprintf("%.2f Hz", x); }, + mode: "line-vertical", // undocumented, just a default differing from TK.ResponseHanlde + preferences: [ "top-right", "right", "bottom-right", "top-left", "left", "bottom-left"], // undocumented, just a default differing from TK.ResponseHanlde + }, + initialize: function (options) { + /** + * @member {TK.Filter} TK.CrossoverBand#upper - The filter providing the graphical calculations for the upper graph. + */ + this.upper = new TK.Filter(); + /** + * @member {TK.Filter} TK.CrossoverBand#lower - The filter providing the graphical calculations for the lower graph. + */ + this.lower = new TK.Filter(); + TK.EqBand.prototype.initialize.call(this, options); + /** + * @member {HTMLDivElement} TK.CrossoverBand#element - The main SVG group. + * Has class toolkit-crossoverband. + */ + TK.add_class(this.element, "toolkit-crossoverband"); + + this.set("lower", this.options.lower); + this.set("upper", this.options.upper); + }, + set: function (key, val) { + switch (key) { + case"lower": + this.filter = this.lower; + var r = TK.EqBand.prototype.set.call(this, "type", val); + this.set("mode", "line-vertical"); + return r; + case "upper": + this.filter = this.upper; + var r = TK.EqBand.prototype.set.call(this, "type", val); + this.set("mode", "line-vertical"); + return r; + case "freq": + case "gain": + case "q": + if (this.lower) + this.filter = this.lower; + val = TK.EqBand.prototype.set.call(this, key, val); + this.filter = this.upper; + return TK.EqBand.prototype.set.call(this, key, val); + } + return TK.EqBand.prototype.set.call(this, key, val); + }, +}); + +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/dialog.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/dialog.js new file mode 100644 index 0000000000..a146770e77 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/dialog.js @@ -0,0 +1,219 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function (w, TK) { + +function autoclose_cb(e) { + var curr = e.target; + while (curr) { + // TODO: if a dialog is opened out of a dialog both should avoid + // closing any of those on click. former version: + //if (curr === this.element) return; + // this closes tagger in Cabasa Dante Tagger when interacting + // with the colorpicker. + // workaround for the moment: + // don't close on click on any dialog + if (curr.classList.contains("toolkit-dialog")) return; + curr = curr.parentElement; + } + this.close(); +} + +function activate_autoclose() { + if (this._autoclose_active) return; + document.body.addEventListener("click", this._autoclose_cb); + this._autoclose_active = true; +} + +function deactivate_autoclose() { + if (!this._autoclose_active) return; + document.body.removeEventListener("click", this._autoclose_cb); + this._autoclose_active = false; +} + +TK.Dialog = TK.class({ +/** + * TK.Dialog provides a hovering area which can be closed by clicking/tapping + * anywhere on the screen. It can be automatically pushed to the topmost + * DOM position as a child of an AWML-ROOT or the BODY element. On close + * it can be removed from the DOM. The {@link TK.Anchor}-functionality + * makes positioning the dialog window straight forward. + * + * @class TK.Dialog + * + * @extends TK.Container + * @implments TK.Anchor + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {Boolean} [options.visible=true] - Hide or show the dialog. + * @property {String} [options.anchor="top-left"] - Origin of `x` and `y` coordinates. + * @property {Number} [options.x=0] - X-position of the dialog. + * @property {Number} [options.y=0] - Y-position of the dialog. + * @property {boolean} [options.auto_close=false] - Set dialog to `visible=false` if clicked outside in the document. + * @property {boolean} [options.auto_remove=false] - Remove the dialogs DOM node after setting `visible=false`. + * @property {boolean} [options.toplevel=false] - Add the dialog DOM node to the topmost position in DOM on `visible=true`. Topmost means either a parenting `AWML-ROOT` or the `BODY` node. + * + */ + _class: "Dialog", + Extends: TK.Container, + Implements: TK.Anchor, + _options: Object.assign(Object.create(TK.Container.prototype._options), { + visible: "boolean", + anchor: "string", + x: "number", + y: "number", + auto_close: "boolean", + auto_remove: "boolean", + toplevel: "boolean", + }), + options: { + visible: true, + anchor: "top-left", + x: 0, + y: 0, + auto_close: false, + auto_remove: false, + toplevel: false, + }, + static_events: { + hide: function() { + deactivate_autoclose.call(this); + if (this.options.auto_remove) + this.element.remove(); + this.fire_event("close"); + }, + set_display_state: function(val) { + var O = this.options; + + if (val === "show") { + if (O.auto_close) + activate_autoclose.call(this); + this.trigger_resize(); + } else { + deactivate_autoclose.call(this); + } + + if (val === "showing") { + var C = O.container; + if (C) C.appendChild(this.element); + this.reposition(); + } + + }, + set_auto_close: function(val) { + if (val) { + if (!this.hidden()) activate_autoclose.call(this); + } else { + deactivate_autoclose.call(this); + } + }, + set_visible: function (val) { + var O = this.options; + if (val) { + deactivate_autoclose.call(this); + if (O.toplevel && O.container.tagName !== "AWML-ROOT" && O.container.tagName !== "BODY") { + var p = this.element; + while ((p = p.parentElement) && p.tagName !== "AWML-ROOT" && p.tagName !== "BODY") {}; + this.set("container", p); + } + this.show(); + } else { + O.container = this.element.parentElement; + this.hide(); + } + }, + }, + initialize: function (options) { + TK.Container.prototype.initialize.call(this, options); + TK.add_class(this.element, "toolkit-dialog"); + var O = this.options; + /* This cannot be a default option because document.body + * is not defined there */ + if (!O.container) O.container = window.document.body; + this._autoclose_active = false; + this._autoclose_cb = autoclose_cb.bind(this); + this.set('visible', O.visible); + if (O.visible) + this.force_show() + else + this.force_hide() + }, + resize: function() { + if (this.options.visible) + this.reposition(); + }, + redraw: function () { + TK.Container.prototype.redraw.call(this); + var I = this.invalid; + var O = this.options; + var E = this.element; + if (I.x || I.y || I.anchor) { + var bodybox = document.body.getBoundingClientRect(); + var sw = bodybox.width; + var sh = bodybox.height; + var box = this.element.getBoundingClientRect(); + I.x = I.y = I.anchor = false; + var box = E.getBoundingClientRect(); + var pos = this.translate_anchor(O.anchor, O.x, O.y, -box.width, -box.height); + pos.x = Math.min(sw - box.width, Math.max(0, pos.x)); + pos.y = Math.min(sh - box.height, Math.max(0, pos.y)); + E.style.left = pos.x + "px" + E.style.top = pos.y + "px" + } + }, + /** + * Open the dialog. Optionally set x and y position regarding `anchor`. + * + * @method TK.Dialog#open + * + * @param {Number} [x] - New X-position of the dialog. + * @param {Number} [y] - New Y-position of the dialog. + */ + open: function (x, y) { + + this.fire_event("open"); + this.userset("visible", true); + if (typeof x !== "undefined") + this.set("x", x); + if (typeof y !== "undefined") + this.set("y", y); + }, + /** + * Close the dialog. The node is removed from DOM if `auto_remove` is set to `true`. + * + * @method TK.Dialog#close + */ + close: function () { + this.userset("visible", false); + }, + /** + * Reposition the dialog to the current `x` and `y` position. + * + * @method TK.Dialog#reposition + */ + reposition: function () { + var O = this.options; + this.set("x", O.x); + this.set("y", O.y); + } +}); + + +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/dynamics.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/dynamics.js new file mode 100644 index 0000000000..24af338ec2 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/dynamics.js @@ -0,0 +1,284 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function(w, TK){ +function range_set(value, key) { + this.range_x.set(key, value); + this.range_y.set(key, value); +} +TK.Dynamics = TK.class({ + /** + * TK.Dynamics are based on {@link TK.Chart} and display the characteristics of dynamic + * processors. They are square widgets drawing a {@link TK.Grid} automatically based on + * the range. + * + * @class TK.Dynamics + * + * @extends TK.Chart + * + * @property {Object} options + * + * @param {Number} [options.min=-96] - Minimum decibels to display. + * @param {Number} [options.max=24] - Maximum decibels to display. + * @param {String} [options.scale="linear"] - Scale of the display, see {@link TK.Range} for details. + * @param {String} [options.type=false] - Type of the dynamics: compressor, expander, gate, limiter or false to draw your own graph. + * @param {Number} [options.threshold=0] - Threshold of the dynamics. + * @param {Number} [options.ratio=1] - Ratio of the dynamics. + * @param {Number} [options.makeup=0] - Makeup of the dynamics. This raises the whole graph after all other parameters are applied. + * @param {Number} [options.range=0] - Range of the dynamics. Only used in type expander. The maximum gain reduction. + * @param {Number} [options.gain=0] - Input gain of the dynamics. + * @param {Number} [options.reference=0] - Input reference of the dynamics. + * @param {Function} [options.grid_labels=function (val) { return val + (!val ? "dB":""); }] - Callback to format the labels of the {@link TK.Grid}. + * @param {Number} [options.db_grid=12] - Draw a grid line every [n] decibels. + */ + _class: "Dynamics", + Extends: TK.Chart, + _options: Object.assign(Object.create(TK.Chart.prototype._options), { + size: "number", // deprecated, undocumented. Is set via CSS. + min: "number", + max: "number", + scale: "string", + type: "string", + threshold: "number", + ratio: "number", + makeup: "number", + range: "number", + gain: "number", + reference: "number", + grid_labels: "function", + db_grid: "number", + }), + options: { + db_grid: 12, + min: -96, + max: 24, + scale: "linear", + type: false, + threshold: 0, + ratio: 1, + makeup: 0, + range: 0, + gain: 0, + reference: 0, + grid_labels: function (val) { return val + (!val ? "dB":""); } + }, + static_events: { + set_size: function(value) { + TK.warn("using deprecated 'size' option"); + this.set("width", value); + this.set("height", value); + }, + set_min: range_set, + set_max: range_set, + set_scale: range_set, + }, + initialize: function (options) { + TK.Chart.prototype.initialize.call(this, options, true); + var O = this.options; + /** + * @member {HTMLDivElement} TK.Dynamics#element - The main DIV container. + * Has class toolkit-dynamics. + */ + TK.add_class(this.element, "toolkit-dynamics"); + this.set("scale", O.scale); + if (O.size) this.set("size", O.size); + this.set("min", O.min); + this.set("max", O.max); + /** + * @member {TK.Graph} TK.Dynamics#steady - The graph drawing the zero line. Has class toolkit-steady + */ + this.steady = this.add_graph({ + dots: [{x:O.min, y:O.min}, + {x:O.max, y:O.max}], + "class": "toolkit-steady", + mode: "line" + }); + }, + + redraw: function () { + var O = this.options; + var I = this.invalid; + + TK.Chart.prototype.redraw.call(this); + + if (I.validate("size", "min", "max", "scale")) { + var grid_x = []; + var grid_y = []; + var min = this.range_x.get("min"); + var max = this.range_x.get("max"); + var step = O.db_grid; + var cls; + for (var i = min; i <= max; i += step) { + cls = i ? "" : "toolkit-highlight"; + grid_x.push({ + pos: i, + label: i === min ? "" : O.grid_labels(i), + "class": cls + }); + grid_y.push({ + pos: i, + label: i === min ? "" : O.grid_labels(i), + "class": cls + }); + } + if (this.grid) { + this.grid.set("grid_x", grid_x); + this.grid.set("grid_y", grid_y); + } + + if (this.steady) + this.steady.set("dots", [{x:O.min, y:O.min}, {x:O.max, y:O.max}]); + } + + if (I.type) { + if (O._last_type) + TK.remove_class(this.element, "toolkit-" + O._last_type); + TK.add_class(this.element, "toolkit-" + O.type); + } + + if (I.validate("ratio", "threshold", "range", "makeup", "gain", "reference")) { + this.draw_graph(); + } + }, + + resize: function() { + var O = this.options; + var E = this.element; + var S = this.svg; + + /* bypass the Chart resize logic here */ + TK.Widget.prototype.resize.call(this); + + var tmp = TK.css_space(S, "border", "padding"); + var w = TK.inner_width(E) - tmp.left - tmp.right; + var h = TK.inner_height(E) - tmp.top - tmp.bottom; + + var s = Math.min(h, w); + + if (s > 0 && s !== O._width) { + this.set("_width", s); + this.set("_height", s); + this.range_x.set("basis", s); + this.range_y.set("basis", s); + } + }, + + draw_graph: function () { + var O = this.options; + if (O.type === false) return; + if (!this.graph) { + this.graph = this.add_graph({ + dots: [{x: O.min, y: O.min}, + {x: O.max, y: O.max}] + }); + } + var curve = []; + var range = O.range; + var ratio = O.ratio; + var thres = O.threshold; + var gain = O.gain; + var ref = O.reference; + var makeup = O.makeup; + var min = O.min; + var max = O.max; + var s; + if (ref == 0) + { + s = 0; + } + else if (!isFinite(ratio)) + { + s = ref; + } + else + { + s = (1 / (Math.max(ratio, 1.001) - 1)) * ratio * ref; + } + var l = 5; // estimated width of line. dirty workaround for + // keeping the line end out of sight in case + // salient point is outside the visible are + switch (O.type) { + case "compressor": + // entry point + curve.push({x: min - l, + y: min + makeup - gain + ref - l}); + // salient point + curve.push({x: thres + gain - s, + y: thres + makeup - s + ref}); + // exit point + if (isFinite(ratio) && ratio > 0) { + curve.push({x: max, + y: thres + makeup + (max - thres - gain) / ratio + }); + } else if (ratio === 0) { + curve.push({x: thres, + y: max + }); + } else { + curve.push({x: max, + y: thres + makeup + }); + } + + break; + case "limiter": + curve.push({x: min, + y: min + makeup - gain}); + curve.push({x: thres + gain, + y: thres + makeup}); + curve.push({x: max, + y: thres + makeup}); + break; + case "gate": + curve.push({x: thres, + y: min}); + curve.push({x: thres, + y: thres + makeup}); + curve.push({x: max, + y: max + makeup}); + break; + case "expander": + if (O.ratio !== 1) { + curve.push({x: min, + y: min + makeup + range}); + + var y = (ratio * range + (ratio - 1) * thres) / (ratio - 1); + curve.push({x: y - range, + y: y + makeup}); + curve.push({x: thres, + y: thres + makeup}); + } + else + curve.push({x: min, + y: min + makeup}); + curve.push({x: max, + y: max + makeup}); + break; + default: + TK.warn("Unsupported type", O.type); + } + this.graph.set("dots", curve); + }, + set: function (key, val) { + if (key == "type") + this.options._last_type = this.options.type; + return TK.Chart.prototype.set.call(this, key, val); + }, +}); +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/equalizer.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/equalizer.js new file mode 100644 index 0000000000..cca9645ed9 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/equalizer.js @@ -0,0 +1,346 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +"use strict"; +(function(w, TK){ +function fast_draw_plinear(X, Y) { + var ret = []; + var i, len = X.length; + var dy = 0, x, y, tmp; + + var accuracy = 20; + var c = 0; + + if (len < 2) return ""; + + x = +X[0]; + y = +Y[0]; + + ret.push("M", x.toFixed(2), ",", y.toFixed(2)); + + x = +X[1]; + y = +Y[1]; + + dy = ((y - Y[0])*accuracy)|0; + + for (i = 2; i < len; i++) { + tmp = ((Y[i] - y)*accuracy)|0; + if (tmp !== dy) { + ret.push("L", x.toFixed(2), ",", y.toFixed(2)); + dy = tmp; + c++; + } + x = +X[i]; + y = +Y[i]; + } + + ret.push("L", x.toFixed(2), ",", y.toFixed(2)); + + return ret.join(""); +} +function draw_graph (graph, bands) { + var O = this.options; + var c = 0; + var end = this.range_x.get("basis") | 0; + var step = O.accuracy; + var over = O.oversampling; + var thres = O.threshold; + var x_px_to_val = this.range_x.px2val; + var y_val_to_px = this.range_y.val2px; + var i, j, k; + var x, y; + var pursue; + var diff; + + var X = new Array(end / step); + for (i = 0; i < X.length; i++) { + X[i] = c; + c += step; + } + var Y = new Array(end / step); + var y; + + for (i = 0; i < X.length; i++) { + x = x_px_to_val(X[i]); + y = 0.0; + for (j = 0; j < bands.length; j++) y += bands[j](x); + Y[i] = y_val_to_px(y); + var diff = Math.abs(Y[i] - Y[i-1]) >= thres; + if (i && over > 1 && (diff || pursue)) { + if (diff) pursue = true; + else if (!diff && pursue) pursue = false; + for (k = 1; k < over; k++) { + x = X[i-k] + ((step / over) * k); + X.splice(i, 0, x); + x = x_px_to_val(x); + y = 0.0; + for (j = 0; j < bands.length; j++) y += bands[j](x); + Y.splice(i, 0, y_val_to_px(y)); + i++; + } + } + + if (!isFinite(Y[i])) { + TK.warn("Singular filter in Equalizer."); + graph.set("dots", void(0)); + return; + } + } + graph.set("dots", fast_draw_plinear(X, Y)); +} +function invalidate_bands() { + this.invalid.bands = true; + this.trigger_draw(); +} +function show_bands() { + var b = this.bands; + for (var i = 0; i < b.length; i ++) { + this.add_child(b[i]); + } +} +function hide_bands() { + var b = this.bands; + for (var i = 0; i < b.length; i ++) { + this.remove_child(b[i]); + } +} +TK.Equalizer = TK.class({ + /** + * TK.Equalizer is a {@link TK.ResponseHandler}, utilizing {@link TK.EqBand}s instead of + * simple {@link TK.ResponseHandle}s. + * + * @property {Object} options + * + * @param {Number} [options.accuracy=1] - The distance between points on + * the x axis. Reduces CPU load in favour of accuracy and smoothness. + * @param {Array} [options.bands=[]] - A list of bands to add on init. + * @param {Boolean} [options.show_bands=true] - Show or hide all bands. + * @param {Number} [options.oversampling=5] - If slope of the curve is too + * steep, oversample n times in order to not miss e.g. notch filters. + * @param {Number} [options.threshold=5] - Steepness of slope to oversample, + * i.e. y pixels difference per x pixel + * @class TK.Equalizer + * + * @extends TK.ResponseHandler + */ + _class: "Equalizer", + Extends: TK.ResponseHandler, + _options: Object.assign(Object.create(TK.ResponseHandler.prototype._options), { + accuracy: "number", + oversampling: "number", + threshold: "number", + bands: "array", + show_bands: "boolean", + }), + options: { + accuracy: 1, // the distance between points of curves on the x axis + oversampling: 4, // if slope of the curve is too steep, oversample + // n times in order to not miss a notch filter + threshold: 10, // steepness of slope, i.e. amount of y pixels difference + bands: [], // list of bands to create on init + show_bands: true, + }, + static_events: { + set_bands: function(value) { + if (this.bands.length) this.remove_bands(); + this.add_bands(value); + }, + set_show_bands: function(value) { + (value ? show_bands : hide_bands).call(this); + }, + }, + + initialize: function (options) { + TK.ResponseHandler.prototype.initialize.call(this, options); + /** + * @member {Array} TK.Equalizer#bands - Array of {@link TK.EqBand} instances. + */ + this.bands = this.handles; + + /** + * @member {HTMLDivElement} TK.Equalizer#element - The main DIV container. + * Has class toolkit-equalizer. + */ + TK.add_class(this.element, "toolkit-equalizer"); + + /** + * @member {SVGGroup} TK.Equalizer#_bands - The SVG group containing all the bands SVG elements. + * Has class toolkit-eqbands. + */ + this._bands = this._handles; + TK.add_class(this._bands, "toolkit-eqbands"); + + /** + * @member {TK.Graph} TK.Equalizer#baseline - The graph drawing the zero line. + * Has class toolkit-baseline + */ + this.baseline = this.add_graph({ + range_x: this.range_x, + range_y: this.range_y, + container: this._bands, + dots: [{x: 20, y: 0}, {x: 20000, y: 0}], + "class": "toolkit-baseline" + }); + this.add_bands(this.options.bands); + }, + + destroy: function () { + this.empty(); // Arne: ??? <- Markus: removes all graphs, defined in Chart + this._bands.remove(); + TK.ResponseHandler.prototype.destroy.call(this); + }, + redraw: function () { + var I = this.invalid; + var O = this.options; + TK.ResponseHandler.prototype.redraw.call(this); + if (I.validate("bands", "accuracy")) { + if (this.baseline) { + var f = []; + for (var i = 0; i < this.bands.length; i++) { + if (this.bands[i].get("active")) { + f.push(this.bands[i].filter.get_freq2gain()); + } + } + draw_graph.call(this, this.baseline, f); + } + } + + if (I.show_bands) { + I.show_bands = false; + if (O.show_bands) { + this._bands.style.removeProperty("display"); + } else { + this._bands.style.display = "none"; + } + } + }, + resize: function () { + invalidate_bands.call(this); + TK.ResponseHandler.prototype.resize.call(this); + }, + /** + * Add a new band to the equalizer. Options is an object containing + * options for the {@link TK.EqBand} + * + * @method TK.Equalizer#add_band + * + * @param {Object} [options={ }] - An object containing initial options for the {@link TK.EqBand}. + * @param {Object} [type=TK.EqBand] - A widget class to be used for the new band. + * + * @emits TK.Equalizer#bandadded + */ + add_band: function (options, type) { + var b; + type = type || TK.EqBand; + if (type.prototype.isPrototypeOf(options)) { + b = options; + } else { + options.container = this._bands; + if (options.range_x === void(0)) + options.range_x = function () { return this.range_x; }.bind(this); + if (options.range_y === void(0)) + options.range_y = function () { return this.range_y; }.bind(this); + if (options.range_z === void(0)) + options.range_z = function () { return this.range_z; }.bind(this); + + options.intersect = this.intersect.bind(this); + b = new type(options); + } + + this.bands.push(b); + b.add_event("set", invalidate_bands.bind(this)); + /** + * Is fired when a new band was added. + * + * @event TK.Equalizer#bandadded + * + * @param {TK.Band} band - The {@link TK.EqBand} which was added. + */ + this.fire_event("bandadded", b); + if (this.options.show_bands) + this.add_child(b); + invalidate_bands.call(this); + return b; + }, + /** + * Add multiple new {@link TK.EqBand}s to the equalizer. Options is an array + * of objects containing options for the new instances of {@link TK.EqBand} + * + * @method TK.Equalizer#add_bands + * + * @param {Array} options - An array of options objects for the {@link TK.EqBand}. + * @param {Object} [type=TK.EqBand] - A widget class to be used for the new band. + */ + add_bands: function (bands, type) { + for (var i = 0; i < bands.length; i++) + this.add_band(bands[i], type); + }, + /** + * Remove a band from the widget. + * + * @method TK.Equalizer#remove_handle + * + * @param {TK.EqBand} band - The {@link TK.EqBand} to remove. + * + * @emits TK.Equalizer#bandremoved + */ + remove_band: function (h) { + for (var i = 0; i < this.bands.length; i++) { + if (this.bands[i] === h) { + if (this.options.show_bands) + this.remove_child(h); + + this.bands.splice(i, 1); + /** + * Is fired when a band was removed. + * + * @event TK.Equalizer#bandremoved + * + * @param {TK.EqBand} band - The {@link TK.EqBand} which was removed. + */ + this.fire_event("bandremoved", h); + h.destroy(); + break; + } + } + }, + /** + * Remove multiple {@link TK.EqBand} from the equalizer. Options is an array + * of {@link TK.EqBand} instances. + * + * @method TK.Equalizer#remove_bands + * + * @param {Array} bands - An array of {@link TK.EqBand} instances. + */ + remove_bands: function () { + while (this.bands.length) { + this.remove_band(this.bands[0]); + } + this.bands = []; + /** + * Is fired when all bands are removed. + * + * @event TK.Equalizer#emptied + */ + this.fire_event("emptied"); + invalidate_bands.call(this); + }, + _draw_graph: draw_graph, +}); +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/expander.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/expander.js new file mode 100644 index 0000000000..61a6cdfbb1 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/expander.js @@ -0,0 +1,272 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function(w, TK){ +function toggle(e) { + var self = this.parent; + e.preventDefault(); + e.stopPropagation(); + return collapse.call(self, !self.options.expanded); +} +function collapse(state) { + this.userset("expanded", state); + return false; +} +function visible_when_expanded(widget) { + var v = widget.options._expanded; + return v !== false; +} +function visible_when_collapsed(widget) { + var v = widget.options._collapsed; + return v === true; +} +function is_visible(widget) { + var value = this.options.always_expanded || this.options.expanded; + + if (value) { + return visible_when_expanded(widget); + } else { + return visible_when_collapsed(widget); + } +} +function changed_expanded(value) { + var group = this.options.group; + var other_expander; + var grp; + + if (group) { + grp = expander_groups[group]; + if (value) { + other_expander = grp.active; + grp.active = this; + if (other_expander && other_expander !== this) + other_expander.set("expanded", false); + } else if (grp.active === this) { + grp.active = false; + if (grp.default) grp.default.set("expanded", true); + } + } + update_visibility.call(this); +} +function add_to_group(group) { + var grp; + var O = this.options; + if (!(grp = expander_groups[group])) + expander_groups[group] = grp = { active: false, default: false }; + + if (O.group_default) { + grp.default = this; + if (!grp.active) { + this.set("expanded", true); + return; + } + } + + if (O.expanded) + changed_expanded.call(this, O.expanded); +} + +function remove_from_group(group) { + var grp = expander_groups[group]; + + if (grp.default === this) grp.default = false; + if (grp.active === this) { + grp.active = false; + if (grp.default) grp.default.set("expanded", true); + } +} +function remove_group_default(group) { + if (!group) return; + var grp = expander_groups[group]; + grp.default = false; +} +function update_visibility() { + var C = this.children; + var value = this.options.always_expanded || this.options.expanded; + + if (C) { + var test = value ? visible_when_expanded : visible_when_collapsed; + for (var i = 0; i < C.length; i++) { + if (test(C[i])) + this.show_child(i); + else + this.hide_child(i); + } + } + + if (value) { + this.fire_event("expand"); + /** + * Is fired when the expander expands. + * + * @event TK.Expander#expand + */ + } else { + /** + * Is fired when the expander collapses. + * + * @event TK.Expander#collapse + */ + this.fire_event("collapse"); + } +} +var expander_groups = { }; +w.eg = expander_groups; +TK.Expander = TK.class({ + /** + * TK.Expander is a container which can be toggled between two different states, + * expanded and collapsed. It can be used to implement overlay popups, but it is + * not limited to that application. + * In expanded mode the container has the class toolkit-expanded. + * Child widgets are shown or hidden depending on the state of the two pseudo + * options _expanded and _collapsed. If a child widget + * of the expander has _expanded set to true it will be shown in + * expanded state. If a child widget has _collapsed set to false, it + * will be shown in collapsed state. This feature can be used to make interfaces + * more reactive. + * + * @class TK.Expander + * + * @extends TK.Container + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {Boolean} [options.expanded=false] - The state of the widget. + * @property {Boolean} [options.always_expanded=false] - This essentially overwrites + * the expanded option. This can be used to switch this widget to be + * always expanded, e.g. when the screen size is big enough. + * @property {String} [options.group=""] - If set, this expander is grouped together with + * all other expanders of the same group name. At most one expander of the same group + * can be open at one time. + * @property {Boolean} [options.group_default=false] - If set, this expander is expanded + * if all other group members are collapsed. + * @property {String} [options.icon=""] - Icon of the {@link TK.Button} which toggles expanded state. + * @property {String} [options.label=""] - Label of the {@link TK.Button} which toggles expanded state. + * @property {Boolean} [options.show_button=true] - Set to `false` to hide the {@link TK.Button} toggling expanded state. + */ + _class: "Expander", + _options: Object.assign(Object.create(TK.Container.prototype._options), { + expanded: "boolean", + always_expanded: "boolean", + group: "string", + group_default: "boolean", + label: "string", + icon: "string", + }), + options: { + expanded: false, + always_expanded: false, + group_default: false, + label: "", + icon: "", + }, + static_events: { + set_expanded: changed_expanded, + set_always_expanded: update_visibility, + set_group: function(value) { + if (value) add_to_group.call(this, value); + } + }, + Extends: TK.Container, + /** + * Toggles the collapsed state of the widget. + * + * @method TK.Expander#toggle + */ + toggle: function() { + toggle.call(this); + }, + redraw: function() { + var I = this.invalid; + var O = this.options; + + TK.Container.prototype.redraw.call(this); + + if (I.always_expanded) { + this[O.always_expanded ? "add_class" : "remove_class"]("toolkit-always-expanded"); + } + + if (I.expanded || I.always_expanded) { + I.always_expanded = I.expanded = false; + var v = O.always_expanded || O.expanded; + this[v ? "add_class" : "remove_class"]("toolkit-expanded"); + this.trigger_resize(); + } + }, + initialize: function (options) { + TK.Container.prototype.initialize.call(this, options); + /** + * @member {HTMLDivElement} TK.Expander#element - The main DIV container. + * Has class toolkit-expander. + */ + TK.add_class(this.element, "toolkit-expander"); + + this._update_visibility = update_visibility.bind(this); + + if (this.options.group) add_to_group.call(this, this.options.group); + + this.set("expanded", this.options.expanded); + this.set("always_expanded", this.options.always_expanded); + + }, + add_child: function(child) { + TK.Container.prototype.add_child.call(this, child); + if (!is_visible.call(this, child)) this.hide_child(child); + child.add_event("set__expanded", this._update_visibility); + child.add_event("set__collapsed", this._update_visibility); + }, + remove_child: function(child) { + TK.Container.prototype.remove_child.call(this, child); + child.remove_event("set__expanded", this._update_visibility); + child.remove_event("set__collapsed", this._update_visibility); + }, + set: function(key, value) { + var group; + if (key === "group") { + group = this.options.group; + // this is reached from init, where this element was never added + // to the group. + if (group && value !== group) remove_from_group.call(this, group); + } else if (key === "group_default") { + if (!value && this.options.group_default) + remove_group_default.call(this, this.options.group); + } + return TK.Container.prototype.set.call(this, key, value); + }, +}); +/** + * @member {TK.Button} TK.Expander#button - The button for toggling the state of the expander. + */ +TK.ChildWidget(TK.Expander, "button", { + create: TK.Button, + show: true, + map_options: { + label: "label", + icon: "icon", + }, + default_options: { + _expanded: true, + _collapsed: true, + class: "toolkit-toggle-expand" + }, + static_events: { + click: toggle, + }, +}); +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/fader.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/fader.js new file mode 100644 index 0000000000..df3692b27f --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/fader.js @@ -0,0 +1,411 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + + /** + * The useraction event is emitted when a widget gets modified by user interaction. + * The event is emitted for the option value. + * + * @event TK.Fader#useraction + * + * @param {string} name - The name of the option which was changed due to the users action + * @param {mixed} value - The new value of the option + */ +"use strict"; +(function(w, TK){ + +function vert(O) { + return O.layout === "left" || O.layout === "right"; +} +function get_value(ev) { + var is_vertical = vert(this.options); + var pos, real, hsize, pad; + hsize = this._handle_size / 2; + pad = this._padding; + + if (is_vertical) { + real = this.options.basis - (ev.offsetY - hsize) + pad.bottom; + } else { + real = ev.offsetX - hsize + pad.left; + } + return this.px2val(real); +} +function tooltip_by_position(ev, tt) { + if (this._handle.contains(ev.target)) { + tooltip_by_value.call(this, ev, tt); + return; + } + var val = this.snap(get_value.call(this, ev)); + TK.set_text(tt, this.options.tooltip(val)); +} +function tooltip_by_value(ev, tt) { + TK.set_text(tt, this.options.tooltip(this.options.value)); +} +function mouseenter (ev) { + if (!this.options.tooltip) return; + TK.tooltip.add(1, this.tooltip_by_position); +} +function clicked(ev) { + var value; + if (this._handle.contains(ev.target)) return; + if (this.value && this.value.element.contains(ev.target)) return; + if (this.label && this.label.element.contains(ev.target)) return; + if (this.scale && this.scale.element.contains(ev.target)) return; + value = this.userset("value", get_value.call(this, ev)); + if (this.options.tooltip && TK.tooltip._entry) + TK.set_text(TK.tooltip._entry, this.options.tooltip(this.options.value)); +} +function mouseleave (ev) { + TK.tooltip.remove(1, this.tooltip_by_position); +} +function startdrag(ev) { + if (!this.options.tooltip) return; + TK.tooltip.add(0, this.tooltip_by_value); +} +function stopdrag(ev) { + TK.tooltip.remove(0, this.tooltip_by_value); +} +function scrolling(ev) { + if (!this.options.tooltip) return; + TK.set_text(TK.tooltip._entry, this.options.tooltip(this.options.value)); +} +function dblclick(ev) { + this.userset("value", this.options.reset); + /** + * Is fired when the handle receives a double click. + * + * @event TK.Fader#doubleclick + * + * @param {number} value - The value of the {@link TK.Fader}. + */ + this.fire_event("doubleclick", this.options.value); +} +function activate_tooltip() { + if (!this.tooltip_by_position) { + this.tooltip_by_position = tooltip_by_position.bind(this); + this.tooltip_by_value = tooltip_by_value.bind(this); + this.__startdrag = startdrag.bind(this); + this.__stopdrag = stopdrag.bind(this); + this.__scrolling = scrolling.bind(this); + } + this.add_event("mouseenter", mouseenter); + this.add_event("mouseleave", mouseleave); + this.drag.add_event("startdrag", this.__startdrag); + this.drag.add_event("stopdrag", this.__stopdrag); + this.scroll.add_event("scrolling", this.__scrolling); +} + +function deactivate_tooltip() { + if (!this.tooltip_by_position) return; + TK.tooltip.remove(0, this.tooltip_by_value); + TK.tooltip.remove(1, this.tooltip_by_position); + this.remove_event("mouseenter", mouseenter); + this.remove_event("mouseleave", mouseleave); + this.drag.remove_event("startdrag", this.__startdrag); + this.drag.remove_event("stopdrag", this.__stopdrag); + this.scroll.remove_event("scrolling", this.__scrolling); +} +/** + * TK.Fader is a slidable control with a {@link TK.Scale} next to it which + * can be both dragged and scrolled. TK.Fader implements {@link TK.Ranged}, + * {@link TK.Warning} and {@link TK.GlobalCursor} and inherits their options. + * A {@link TK.Label} and a {@link TK.Value} are available optionally. + * + * @class TK.Fader + * + * @extends TK.Widget + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {Number} [options.value] - The faders position. This options is + * modified by user interaction. + * @property {Function} [options.tooltip=false] - An optional formatting function for + * the tooltip value. The tooltip will show the value the mouse cursor is + * currently hovering over. If this option is not set, no tooltip will be shown. + * @property {Boolean} [options.bind_click=false] - If true, a click + * on the fader will move the handle to the pointed position. + * @property {Boolean} [options.bind_dblclick=true] - If true, a dblclick + * on the fader will reset the fader value to options.reset. + * @property {Number} [options.reset=options.value] - The reset value, which is used by + * the dblclick event and the {@link TK.Fader#reset} method. + * @property {Boolean} [options.show_scale=true] - If true, a {@link TK.Scale} is added to the fader. + * @property {Boolean} [options.show_value=false] - If true, a {@link TK.Value} widget is added to the fader. + * @property {String|Boolean} [options.label=false] - Add a label to the fader. Set to `false` to remove the label from the DOM. + */ +TK.Fader = TK.class({ + _class: "Fader", + Extends: TK.Widget, + Implements: [TK.Ranged, TK.Warning, TK.GlobalCursor], + _options: Object.assign(Object.create(TK.Widget.prototype._options), + TK.Ranged.prototype._options, TK.Scale.prototype._options, { + value: "number", + division: "number", + levels: "array", + gap_dots: "number", + gap_labels: "number", + show_labels: "boolean", + labels: "function", + tooltip: "function", + layout: "string", + direction: "int", + reset: "number", + bind_click: "boolean", + bind_dblclick: "boolean", + }), + options: { + value: 0, + division: 1, + levels: [1, 6, 12, 24], + gap_dots: 3, + gap_labels: 40, + show_labels: true, + labels: function (val) { return val.toFixed(2); }, + tooltip: false, + layout: "left", + bind_click: false, + bind_dblclick: true, + label: false, + }, + static_events: { + set_bind_click: function(value) { + if (value) this.add_event("click", clicked); + else this.remove_event("click", clicked); + }, + set_bind_dblclick: function(value) { + if (value) this.add_event("dblclick", dblclick); + else this.remove_event("dblclick", dblclick); + }, + set_tooltip: function(value) { + (value ? activate_tooltip : deactivate_tooltip).call(this); + }, + set_layout: function(value) { + this.options.direction = vert(this.options) ? "vertical" : "horizontal"; + this.drag.set("direction", this.options.direction); + this.scroll.set("direction", this.options.direction); + }, + }, + initialize: function (options) { + this.__tt = false; + TK.Widget.prototype.initialize.call(this, options); + + var E, O = this.options; + + /** + * @member {HTMLDivElement} TK.Fader#element - The main DIV container. + * Has class toolkit-fader. + */ + if (!(E = this.element)) this.element = E = TK.element("div"); + TK.add_class(E, "toolkit-fader"); + this.widgetize(E, true, true, true); + + /** + * @member {HTMLDivElement} TK.Fader#_track - The track for the handle. Has class toolkit-track. + */ + this._track = TK.element("div", "toolkit-track"); + this.element.appendChild(this._track); + + /** + * @member {HTMLDivElement} TK.Fader#_handle - The handle of the fader. Has class toolkit-handle. + */ + this._handle = TK.element("div", "toolkit-handle"); + this._handle_size = 0; + this._track.appendChild(this._handle); + + if (O.reset === void(0)) + O.reset = O.value; + + if (O.direction === void(0)) + O.direction = vert(O) ? "vertical" : "horizontal"; + /** + * @member {TK.DragValue} TK.Fader#drag - Instance of {@link TK.DragValue} used for the handle + * interaction. + */ + this.drag = new TK.DragValue(this, { + node: this._handle, + classes: this.element, + direction: O.direction, + limit: true, + }); + /** + * @member {TK.ScrollValue} TK.Fader#scroll - Instance of {@link TK.ScrollValue} used for the + * handle interaction. + */ + this.scroll = new TK.ScrollValue(this, { + node: this.element, + classes: this.element, + limit: true, + }); + + this.set("bind_click", O.bind_click); + this.set("bind_dblclick", O.bind_dblclick); + this.set("tooltip", O.tooltip); + }, + + redraw: function () { + TK.Widget.prototype.redraw.call(this); + var I = this.invalid; + var O = this.options; + var E = this.element; + var value; + var tmp; + + if (I.layout) { + I.layout = false; + value = O.layout; + TK.remove_class(E, "toolkit-vertical", "toolkit-horizontal", "toolkit-left", + "toolkit-right", "toolkit-top", "toolkit-bottom"); + TK.add_class(E, vert(O) ? "toolkit-vertical" : "toolkit-horizontal"); + TK.add_class(E, "toolkit-"+value); + + if (TK.supports_transform) + this._handle.style.transform = null; + else { + if (vert(O)) + this._handle.style.left = null; + else + this._handle.style.bottom = null; + } + I.value = false; + } + + if (I.validate.apply(I, Object.keys(TK.Ranged.prototype._options)) || I.value) { + I.value = false; + // TODO: value is snapped already in set(). This is not enough for values which are set during + // initialization. + tmp = this.val2px(this.snap(O.value)) + "px" + + if (vert(O)) { + if (TK.supports_transform) + this._handle.style.transform = "translateY(-"+tmp+")"; + else + this._handle.style.bottom = tmp; + } else { + if (TK.supports_transform) + this._handle.style.transform = "translateX("+tmp+")"; + else + this._handle.style.left = tmp; + } + } + }, + resize: function () { + var O = this.options; + var T = this._track, H = this._handle; + var basis; + + TK.Widget.prototype.resize.call(this); + + this._padding = TK.css_space(T, "padding", "border"); + + if (vert(O)) { + this._handle_size = TK.outer_height(H, true); + basis = TK.inner_height(T) - this._handle_size; + } else { + this._handle_size = TK.outer_width(H, true); + basis = TK.inner_width(T) - this._handle_size; + } + + this.set("basis", basis); + }, + destroy: function () { + this._handle.remove(); + TK.Widget.prototype.destroy.call(this); + TK.tooltip.remove(0, this.tooltip_by_value); + TK.tooltip.remove(1, this.tooltip_by_position); + }, + + /** + * Resets the fader value to options.reset. + * + * @method TK.Fader#reset + */ + reset: function() { + this.set("value", this.options.reset); + }, + + // GETTER & SETTER + set: function (key, value) { + if (key === "value") { + if (value > this.options.max || value < this.options.min) + this.warning(this.element); + value = this.snap(value); + } + + return TK.Widget.prototype.set.call(this, key, value); + }, + userset: function (key, value) { + if (key == "value") { + if (value > this.options.max || value < this.options.min) + this.warning(this.element); + value = this.snap(value); + } + return TK.Widget.prototype.userset.call(this, key, value); + } +}); +/** + * @member {TK.Scale} TK.Fader#scale - A {@link TK.Scale} to display a scale next to the fader. + */ +TK.ChildWidget(TK.Fader, "scale", { + create: TK.Scale, + show: true, + inherit_options: true, + toggle_class: true, + static_events: { + set: function(key, value) { + /** + * Is fired when the scale was changed. + * + * @event TK.Fader#scalechanged + * + * @param {string} key - The key of the option. + * @param {mixed} value - The value to which it was set. + */ + if (this.parent) + this.parent.fire_event("scalechanged", key, value); + }, + }, +}); +/** + * @member {TK.Label} TK.Fader#label - A {@link TK.label} to display a title. + */ +TK.ChildWidget(TK.Fader, "label", { + create: TK.Label, + show: false, + toggle_class: true, + option: "label", + map_options: { + label: "label", + }, +}); +/** + * @member {TK.Label} TK.Fader#value - A {@link TK.Value} to display the current value, offering a way to enter a value via keyboard. + */ +TK.ChildWidget(TK.Fader, "value", { + create: TK.Value, + show: false, + static_events: { + "valueset" : function (v) { this.parent.set("value", v); } + }, + map_options: { + value: "value", + format: "format", + }, + toggle_class: true, + userset_delegate: true, +}); + +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/frame.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/frame.js new file mode 100644 index 0000000000..cc65ea2be6 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/frame.js @@ -0,0 +1,59 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function(w, TK){ +/** + * Frame is a {@link TK.Container} with a {@link TK.Label} on top. + * + * @extends TK.Container + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {String|Boolean} [options.label=false] - The label of the frame. Set to `false` to remove it from the DOM. + * + * @class TK.Frame + */ +TK.Frame = TK.class({ + Extends: TK.Container, + _class: "Frame", + _options: Object.create(TK.Container.prototype._options), + options: { + label: false, + }, + initialize: function (options) { + TK.Container.prototype.initialize.call(this, options); + /** + * @member {HTMLDivElement} TK.Frame#element - The main DIV container. + * Has class toolkit-frame. + */ + TK.add_class(this.element, "toolkit-frame"); + }, +}); +/** + * @member {TK.Label} TK.Frame#label - The {@link TK.Label} of the frame. + */ +TK.ChildWidget(TK.Frame, "label", { + create: TK.Label, + option: "label", + inherit_options: true, + default_options: { + class: "toolkit-frame-label" + }, +}); +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/frequencyresponse.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/frequencyresponse.js new file mode 100644 index 0000000000..63975ba831 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/frequencyresponse.js @@ -0,0 +1,160 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function(w, TK){ +function calculate_grid(range, step) { + var min = range.get("min"); + var max = range.get("max"); + var grid = []; + var i, cls; + + + for (i = min; i <= max; i += step) { + if (i == 0) { + cls = "toolkit-highlight"; + } else { + cls = ""; + } + grid.push({ + pos: i, + label: i === min ? "" : i + "dB", + "class": cls + }); + } + + return grid; +} +TK.FrequencyResponse = TK.class({ + /** + * TK.FrequencyResponse is a {@link TK.Chart} drawing frequencies on the x axis and dB + * values on the y axis. This widget automatically draws a {@link TK.Grid} depending + * on the ranges. + * + * @class TK.FrequencyResponse + * + * @extends TK.Chart + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {Number} [options.db_grid=12] - Distance in decibels between y axis grid lines. + * @property {Object} [options.range_x={min:20, max:20000, scale:"frequency"}] - Either a function returning a {@link TK.Range} + * or an object containing options for a new {@link TK.Range} + * @property {Object} [options.range_y={min:-36, max: 36, scale: "linear"}] - Either a function returning a {@link TK.Range} + * or an object containing options for a new {@link TK.Range} + * @property {Array} [options.grid_x=[{pos: 20, label: "20 Hz"}, {pos: 30}, {pos: 40}, {pos: 50}, {pos: 60}, {pos: 70}, {pos: 80}, {pos: 90}, {pos: 100, label: "100 Hz"}, {pos: 200}, {pos: 300}, {pos: 400}, {pos: 500}, {pos: 600}, {pos: 700}, {pos: 800}, {pos: 900}, {pos: 1000, label: "1000 Hz"}, {pos: 2000}, {pos: 3000}, {pos: 4000}, {pos: 5000}, {pos: 6000}, {pos: 7000}, {pos: 8000}, {pos: 9000}, {pos: 10000, label: "10000 Hz"}, {pos: 20000, label: "20000 Hz"}]] - An array containing objects with the following optional members: + * @property {Number} [options.grid_x.pos] - The value where to draw grid line and corresponding label. + * @property {String} [options.grid_x.color] - A valid CSS color string to colorize the elements. + * @property {String} [options.grid_x.class] - A class name for the elements. + * @property {String} [options.grid_x.label] - A label string. + * @property {String} [options.scale="linear"] - The type of the decibels scale. See {@link TK.Range} for more details. + * @property {Number} [options.depth=0] - The depth of the z axis (basis of options.range_z) + */ + _class: "FrequencyResponse", + Extends: TK.Chart, + _options: Object.assign(Object.create(TK.Chart.prototype._options), { + db_grid: "number", + grid_x: "array", + scale: "boolean", + depth: "number", + }), + options: { + db_grid: 12, // dB grid distance + range_x: {min:20, max:20000, scale:"frequency"}, // TK.Range x options + range_y: {min:-36, max: 36, scale: "linear"}, // TK.Range y options + range_z: {min:0.1, max:10, scale:"linear"}, + grid_x: [ + {pos: 20, label: "20Hz"}, + {pos: 30}, + {pos: 40}, + {pos: 50}, + {pos: 60}, + {pos: 70}, + {pos: 80}, + {pos: 90}, + {pos: 100, label: "100Hz"}, + {pos: 200}, + {pos: 300}, + {pos: 400}, + {pos: 500}, + {pos: 600}, + {pos: 700}, + {pos: 800}, + {pos: 900}, + {pos: 1000, label: "1kHz"}, + {pos: 2000}, + {pos: 3000}, + {pos: 4000}, + {pos: 5000}, + {pos: 6000}, + {pos: 7000}, + {pos: 8000}, + {pos: 9000}, + {pos: 10000, label: "10kHz"}, + {pos: 20000, label: "20kHz"}], // Frequency grid + scale: false, // the mode of the + // dB scale + depth: 0, // the depth of the z axis (basis of range_z) + }, + static_events: { + set_scale: function(value, key) { + this.range_y.set("scale", value); + }, + set_db_grid: function(value) { + this.set("grid_y", calculate_grid(this.range_y, value)); + }, + }, + initialize: function (options) { + if (options.scale) + this.set("scale", options.scale, true); + TK.Chart.prototype.initialize.call(this, options); + /** + * @member {HTMLDivElement} TK.FrequencyResponse#element - The main DIV container. + * Has class toolkit-frequency-response. + */ + TK.add_class(this.element, "toolkit-frequency-response"); + /** + * @member {SVGGroup} TK.Chart#_handles - The SVG group containing all handles. + * Has class toolkit-response-handles. + */ + TK.add_class(this._handles, "toolkit-response-handles"); + + // do not overwrite custom grids, please + if (this.options.db_grid && !this.options.grid_y.length) + this.set("db_grid", this.options.db_grid); + this.range_y.add_event("set", function (key, value) { + if (key === "scale") + this.options.scale = value; + }.bind(this)); + if (this.options.depth) this.set("depth", this.options.depth); + }, + + redraw: function() { + var I = this.invalid; + var O = this.options; + + if (I.scale) { + I.scale = false; + // tell chart to redraw + I.ranges = true; + } + + TK.Chart.prototype.redraw.call(this); + }, +}); +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/gauge.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/gauge.js new file mode 100644 index 0000000000..29356ec5f8 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/gauge.js @@ -0,0 +1,171 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function(w, TK){ +function _get_coords_single(deg, inner, pos) { + deg = deg * Math.PI / 180; + return { + x: Math.cos(deg) * inner + pos, + y: Math.sin(deg) * inner + pos + } +} +var format_translate = TK.FORMAT("translate(%f, %f)"); +var format_viewbox = TK.FORMAT("0 0 %d %d"); +/** + * TK.Gauge draws a single {@link TK.Circular} into a SVG image. It inherits + * all options of {@link TK.Circular}. + * + * @class TK.Gauge + * + * @extends TK.Widget + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {Number} [options.x=0] - Displacement of the {@link TK.Circular} + * in horizontal direction. This allows drawing gauges which are only + * represented by a segment of a circle. + * @property {Number} [options.y=0] - Displacement of the {@link TK.Circular} + * in vertical direction. + * @property {Object} [options.title] - Optional gauge title. + * @property {Number} [options.title.pos] - Position inside of the circle in + * degrees. + * @property {String} [options.title.title] - Title string. + * @property {Number} [options.title.margin] - Margin of the title string. + * @property {String} [options.title.align] - Alignment of the title, either + * inner or outer. + */ +TK.Gauge = TK.class({ + _class: "Gauge", + Extends: TK.Widget, + _options: Object.assign(Object.create(TK.Circular.prototype._options), { + width: "number", + height: "number", + title: "object", + }), + options: Object.assign({}, TK.Circular.prototype.options, { + width: 120, // width of the element + height: 120, // height of the svg + size: 120, + title: {pos: 90, margin: 0, align: "inner", title:""} + }), + initialize: function (options) { + if (typeof options.title === "string") + options.title = {title: options.title}; + TK.Widget.prototype.initialize.call(this, options); + var O = this.options; + var E, S; + if (!(E = this.element)) this.element = E = TK.element("div"); + + /** + * @member {SVGImage} TK.Gauge#svg - The main SVG image. + */ + this.svg = S = TK.make_svg("svg"); + + /** + * @member {HTMLDivElement} TK.Gauge#element - The main DIV container. + * Has class toolkit-gauge. + */ + TK.add_class(E, "toolkit-gauge"); + this.widgetize(E, true, true, true); + + /** + * @member {SVGText} TK.Gauge#_title - The title of the gauge. + * Has class toolkit-title. + */ + this._title = TK.make_svg("text", {"class": "toolkit-title"}); + S.appendChild(this._title); + + var co = TK.object_and(O, TK.Circular.prototype._options); + co = TK.object_sub(co, TK.Widget.prototype._options); + co.container = S; + + /** + * @member {TK.Circular} TK.Gauge#circular - The {@link TK.Circular} module. + */ + this.circular = new TK.Circular(co); + this.add_child(this.circular); + this.widgetize(this.element); + E.appendChild(S); + }, + resize: function() { + TK.Widget.prototype.resize.call(this); + this.invalid.title = true; + this.trigger_draw(); + }, + redraw: function() { + var I = this.invalid, O = this.options; + var S = this.svg; + + TK.Widget.prototype.redraw.call(this); + + if (I.validate("width", "height")) { + S.setAttribute("viewBox", format_viewbox(O.width, O.height)); + } + + if (I.validate("title", "size", "x", "y")) { + var _title = this._title; + _title.textContent = O.title.title; + + if (O.title.title) { + TK.S.add(function() { + var t = O.title; + var outer = O.size / 2; + var margin = t.margin; + var align = t.align === "inner"; + var bb = _title.getBoundingClientRect(); + var angle = t.pos % 360; + var outer_p = outer - margin; + var coords = _get_coords_single(angle, outer_p, outer); + + var mx = ((coords.x - outer) / outer_p) + * (bb.width + bb.height / 2.5) / (align ? -2 : 2); + var my = ((coords.y - outer) / outer_p) + * bb.height / (align ? -2 : 2); + + mx += O.x; + my += O.y; + + TK.S.add(function() { + _title.setAttribute("transform", format_translate(coords.x + mx, coords.y + my)); + _title.setAttribute("text-anchor", "middle"); + }.bind(this), 1); + /** + * Is fired when the title changed. + * + * @event TK.Gauge#titledrawn + */ + this.fire_event("titledrawn"); + }.bind(this)); + } + } + }, + + // GETTERS & SETTERS + set: function (key, value) { + if (key === "title") { + if (typeof value === "string") value = {title: value}; + value = Object.assign(this.options.title, value); + } + // TK.Circular does the snapping + if (!TK.Widget.prototype._options[key] && TK.Circular.prototype._options[key]) + value = this.circular.set(key, value); + return TK.Widget.prototype.set.call(this, key, value); + } +}); +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/icon.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/icon.js new file mode 100644 index 0000000000..a17b1759d4 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/icon.js @@ -0,0 +1,88 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +"use strict"; +(function(w, TK){ + +TK.Icon = TK.class({ + /** + * TK.Icon represents a <DIV> element showing either + * icons from the toolkit font or dedicated image files as CSS background. + * + * @class TK.Icon + * + * @extends TK.Widget + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {String} [options.icon] - The icon to show. It can either be + * a string which is interpreted as class name (if [A-Za-z0-9_\-]) or as URI. + */ + _class: "Icon", + Extends: TK.Widget, + _options: Object.assign(Object.create(TK.Widget.prototype._options), { + icon: "string", + }), + options: { + icon: false, + }, + initialize: function (options) { + var E; + TK.Widget.prototype.initialize.call(this, options); + /** + * @member {HTMLDivElement} TK.Icon#element - The main DIV element. Has class toolkit-icon + */ + if (!(E = this.element)) this.element = E = TK.element("div"); + TK.add_class(E, "toolkit-icon"); + this.widgetize(E, true, true, true); + this._icon_old = []; + }, + redraw: function() { + var O = this.options; + var I = this.invalid; + var E = this.element; + + TK.Widget.prototype.redraw.call(this); + + if (I.icon) { + I.icon = false; + var old = this._icon_old; + for (var i = 0; i < old.length; i++) { + if (old[i] && TK.is_class_name(old[i])) { + TK.remove_class(E, old[i]); + } + } + this._icon_old = []; + if (TK.is_class_name(O.icon)) { + E.style["background-image"] = null; + if (O.icon) + TK.add_class(E, O.icon); + } else if (O.icon) { + E.style["background-image"] = "url(\"" + O.icon + "\")"; + } + } + }, + set: function (key, val) { + if (key == "icon") { + this._icon_old.push(this.options.icon); + } + return TK.Widget.prototype.set.call(this, key, val); + }, +}); +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/knob.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/knob.js new file mode 100644 index 0000000000..6283596379 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/knob.js @@ -0,0 +1,212 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +/** + * The useraction event is emitted when a widget gets modified by user interaction. + * The event is emitted for the option value. + * + * @event TK.Knob#useraction + * + * @param {string} name - The name of the option which was changed due to the users action + * @param {mixed} value - The new value of the option + */ + +"use strict"; +(function(w, TK){ +var format_viewbox = TK.FORMAT("0 0 %d %d"); +function dblclick() { + this.userset("value", this.options.reset); + /** + * Is fired when the knob receives a double click in order to reset to initial value. + * + * @event TK.Knob#doubleclick + * + * @param {number} value - The value of the widget. + */ + this.fire_event("doubleclick", this.options.value); +} +function module_range() { + return this.parent.circular; +} +/** + * TK.Knob is a {@link TK.Circular} inside of an SVG which can be + * modified both by dragging and scrolling utilizing {@link TK.DragValue} + * and {@link TK.ScrollValue}. + * It inherits all options of {@link TK.Circular} and {@link TK.DragValue}. + * The options listed below consist of options from the contained widgets, + * only showing the default values. + * + * @class TK.Knob + * + * @extends TK.Widget + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {Object} [options.hand={width: 1, length: 12, margin: 24}] + * @property {Number} [options.margin=13] + * @property {Number} [options.thickness=6] + * @property {Number} [options.step=1] + * @property {Number} [options.shift_up=4] + * @property {Number} [options.shift_down=0.25] + * @property {Object} [options.dot={length: 6, margin: 13, width: 2}] + * @property {Object} [options.marker={thickness: 6, margin: 13}] + * @property {Object} [options.label={margin: 10, align: "outer", format: function(val){return val;}}] + * @property {Number} [options.basis=300] - Distance to drag between min and max. + + */ +TK.Knob = TK.class({ + _class: "Knob", + Extends: TK.Widget, + _options: Object.assign(Object.create(TK.Widget.prototype._options), TK.Circular.prototype._options, + TK.DragValue.prototype._options, { + size: "number", + reset: "number", + }), + options: Object.assign({}, TK.Circular.prototype.options, { + size: 100, + hand: {width: 1, length: 12, margin: 24}, + margin: 13, + thickness: 6, + step: 1, + shift_up: 4, + shift_down: 0.25, + dot: {length: 6, margin: 13, width: 2}, + marker: {thickness: 6, margin: 13}, + label: {margin: 12, align: "outer", format: function(val){return val;}}, + direction: "polar", + rotation: 45, + blind_angle: 20, + basis: 300, + }), + static_events: { + dblclick: dblclick, + }, + initialize: function (options) { + TK.Widget.prototype.initialize.call(this, options); + options = this.options; + var E, S; + /** + * @member {HTMLDivElement} TK.Knob#element - The main DIV container. + * Has class toolkit-knob. + */ + if (!(E = this.element)) this.element = E = TK.element("div") + TK.add_class(E, "toolkit-knob"); + + /** + * @member {SVGImage} TK.Knob#svg - The main SVG image. + */ + this.svg = S = TK.make_svg("svg"); + + var co = TK.object_and(options, TK.Circular.prototype._options); + co = TK.object_sub(co, TK.Widget.prototype._options); + co.container = S; + + /** + * @member {TK.Circular} TK.Knob#circular - The {@link TK.Circular} module. + */ + this.circular = new TK.Circular(co); + + this.widgetize(E, true, true, true); + + /** + * @member {TK.DragValue} TK.Knob#drag - Instance of {@link TK.DragValue} used for the + * interaction. + */ + this.drag = new TK.DragValue(this, { + node: S, + range: module_range, + direction: options.direction, + rotation: options.rotation, + blind_angle: options.blind_angle, + limit: true, + }); + /** + * @member {TK.ScrollValue} TK.Knob#scroll - Instance of {@link TK.ScrollValue} used for the + * interaction. + */ + this.scroll = new TK.ScrollValue(this, { + node: S, + range: module_range, + limit: true, + }); + + E.appendChild(S); + this.set("base", options.base); + if (options.reset === void(0)) + options.reset = options.value; + this.add_child(this.circular); + }, + + get_range: function() { + return this.circular; + }, + + destroy: function () { + this.drag.destroy(); + this.scroll.destroy(); + this.circular.destroy(); + TK.Widget.prototype.destroy.call(this); + }, + + redraw: function() { + var I = this.invalid; + var O = this.options; + + if (I.size) { + I.size = false; + this.svg.setAttribute("viewBox", format_viewbox(O.size, O.size)); + } + + TK.Widget.prototype.redraw.call(this); + }, + /** + * This is an alias for {@link TK.Circular#add_label} of the internal + * circular instance. + * + * @method TK.Knob#add_label + */ + add_label: function(x) { + return this.circular.add_label(x); + }, + + /** + * This is an alias for {@link TK.Circular#remove_label} of the internal + * circular instance. + * + * @method TK.Knob#remove_label + */ + remove_label: function(x) { + this.circular.remove_label(x); + }, + + set: function(key, value) { + if (key === "base") { + if (value === false) value = this.options.min; + } + // TK.Circular does the snapping + if (!TK.Widget.prototype._options[key]) { + if (TK.Circular.prototype._options[key]) + value = this.circular.set(key, value); + if (TK.DragValue.prototype._options[key]) + this.drag.set(key, value); + } + return TK.Widget.prototype.set.call(this, key, value); + }, +}); +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/label.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/label.js new file mode 100644 index 0000000000..343f91c371 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/label.js @@ -0,0 +1,69 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function(w, TK){ +TK.Label = TK.class({ + /** + * TK.Label is a simple text field displaying strings. + * + * @class TK.Label + * + * @extends TK.Widget + * + * @property {Object} options + * + * @param {Mixed} [options.label=""] - The content of the label. Can be formatted via `options.format`. + * @param {Function|Boolean} [options.format=false] - Optional format function. + */ + _class: "Label", + Extends: TK.Widget, + _options: Object.assign(Object.create(TK.Widget.prototype._options), { + label: "string", + format: "function|boolean" + }), + options: { + label: "", + format: false, + }, + initialize: function (options) { + var E; + TK.Widget.prototype.initialize.call(this, options); + /** @member {HTMLDivElement} TK.Label#element - The main DIV container. + * Has class toolkit-label. + */ + if (!(E = this.element)) this.element = E = TK.element("div"); + TK.add_class(E, "toolkit-label"); + this._text = document.createTextNode(""); + E.appendChild(this._text); + this.widgetize(E, true, true, true); + }, + + redraw: function () { + var I = this.invalid; + var O = this.options; + + TK.Widget.prototype.redraw.call(this); + + if (I.label || I.format) { + I.label = I.format = false; + this._text.data = O.format ? O.format.call(this, O.label) : O.label; + } + }, +}); +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/levelmeter.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/levelmeter.js new file mode 100644 index 0000000000..94d9709f5e --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/levelmeter.js @@ -0,0 +1,542 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function(w, TK){ +function vert(O) { + return O.layout === "left" || O.layout === "right"; +} +function clip_timeout() { + var O = this.options; + if (!O.auto_clip || O.auto_clip < 0) return false; + if (this.__cto) return; + if (O.clip) + this.__cto = window.setTimeout(this._reset_clip, O.auto_clip); +} +function peak_timeout() { + var O = this.options; + if (!O.auto_peak || O.auto_peak < 0) return false; + if (this.__pto) window.clearTimeout(this.__pto); + var value = +this.effective_value(); + if (O.peak > O.base && value > O.base || + O.peak < O.base && value < O.base) + this.__pto = window.setTimeout(this._reset_peak, O.auto_peak); +} +function label_timeout() { + var O = this.options; + var peak_label = (0 | O.peak_label); + var base = +O.base; + var label = +O.label; + var value = +this.effective_value(); + + if (peak_label <= 0) return false; + + if (this.__lto) window.clearTimeout(this.__lto); + + if (label > base && value > base || + label < base && value < base) + + this.__lto = window.setTimeout(this._reset_label, peak_label); +} +function top_timeout() { + var O = this.options; + if (!O.auto_hold || O.auto_hold < 0) return false; + if (this.__tto) window.clearTimeout(this.__tto); + if (O.top > O.base) + this.__tto = window.setTimeout( + this._reset_top, + O.auto_hold); + else + this.__tto = null; +} +function bottom_timeout() { + var O = this.options; + if (!O.auto_hold || O.auto_hold < 0) return false; + if (this.__bto) window.clearTimeout(this.__bto); + if (O.bottom < O.base) + this.__bto = window.setTimeout(this._reset_bottom, O.auto_hold); + else + this.__bto = null; +} + +TK.LevelMeter = TK.class({ + /** + * TK.LevelMeter is a fully functional meter bar displaying numerical values. + * TK.LevelMeter is an enhanced {@link TK.MeterBase}'s containing a clip LED, + * a peak pin with value label and hold markers. + * In addition, LevelMeter has an optional falling animation, top and bottom peak + * values and more. + * + * @class TK.LevelMeter + * + * @extends TK.MeterBase + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {Boolean} [options.show_clip=false] - If set to true, show the clipping LED. + * @property {Number} [options.clipping=0] - If clipping is enabled, this is the threshold for the + * clipping effect. + * @property {Integer|Boolean} [options.auto_clip=false] - This is the clipping timeout. If set to + * false automatic clipping is disabled. If set to n the clipping effect + * times out after n ms, if set to -1 it remains forever. + * @property {Boolean} [options.clip=false] - If clipping is enabled, this option is set to + * true when clipping happens. When automatic clipping is disabled, it can be set to + * true to set the clipping state. + * @property {Object} [options.clip_options={}] - Additional options for the {@link TK.State} clip LED. + * @property {Boolean} [options.show_hold=false] - If set to true, show the hold value LED. + * @property {Integer} [options.hold_size=1] - Size of the hold value LED in the number of segments. + * @property {Number|boolean} [options.auto_hold=false] - If set to false the automatic + * hold LED is disabled, if set to n the hold value is reset after n ms and + * if set to -1 the hold value is not reset automatically. + * @property {Number} [options.top=false] - The top hold value. If set to false it will + * equal the meter level. + * @property {Number} [options.bottom=false] - The bottom hold value. This only exists if a + * base value is set and the value falls below the base. + * @property {Boolean} [options.show_peak=false] - If set to true, show the peak label. + * @property {Integer|Boolean} [options.peak_label=false] - If set to false the automatic peak + * label is disabled, if set to n the peak label is reset after n ms and + * if set to -1 it remains forever. + * @property {Function} [options.format_peak=TK.FORMAT("%.2f")] - Formatting function for the peak label. + * @property {Number} [options.falling=0] - If set to a positive number, activates the automatic falling + * animation. The meter level will fall by this amount per frame. + * @property {Number} [options.falling_fps=24] - This is the number of frames of the falling animation. + * It is not an actual frame rate, but instead is used to determine the actual speed of the falling + * animation together with the option falling. + * @property {Number} [options.falling_init=2] - Initial falling delay in number of frames. This option + * can be used to delay the start of the falling animation in order to avoid flickering if internal + * and external falling are combined. + */ + _class: "LevelMeter", + Extends: TK.MeterBase, + _options: Object.assign(Object.create(TK.MeterBase.prototype._options), { + falling: "number", + falling_fps: "number", + falling_init: "number", + peak: "number", + top: "number", + bottom: "number", + hold_size: "int", + show_hold: "boolean", + clipping: "number", + auto_clip: "int|boolean", + auto_peak: "int|boolean", + peak_label: "int", + auto_hold: "int|boolean", + format_peak: "function", + clip_options: "object", + }), + options: { + clip: false, + falling: 0, + falling_fps: 24, + falling_init: 2, + peak: false, + top: false, + bottom: false, + hold_size: 1, + show_hold: false, + clipping: 0, + auto_clip: false, + auto_peak: false, + peak_label: false, + auto_hold: false, + format_peak: TK.FORMAT("%.2f"), + clip_options: {} + }, + static_events: { + set_label: label_timeout, + set_bottom: bottom_timeout, + set_top: top_timeout, + set_peak: peak_timeout, + set_clip: function(value) { + if (value) { + clip_timeout.call(this); + } + }, + set_show_peak: peak_timeout, + set_auto_clip: function(value) { + if (this.__cto && 0|value <=0) + window.clearTimeout(this.__cto); + }, + set_auto_peak: function(value) { + if (this.__pto && 0|value <=0) + window.clearTimeout(this.__pto); + }, + set_peak_label: function(value) { + if (this.__lto && 0|value <=0) + window.clearTimeout(this.__lto); + }, + set_auto_hold: function(value) { + if (this.__tto && 0|value <=0) + window.clearTimeout(this.__tto); + if (this.__bto && 0|value <=0) + window.clearTimeout(this.__bto); + }, + }, + + initialize: function (options) { + /* track the age of the value option */ + this.track_option("value"); + TK.MeterBase.prototype.initialize.call(this, options); + this._reset_label = this.reset_label.bind(this); + this._reset_clip = this.reset_clip.bind(this); + this._reset_peak = this.reset_peak.bind(this); + this._reset_top = this.reset_top.bind(this); + this._reset_bottom = this.reset_bottom.bind(this); + + /** + * @member {HTMLDivElement} TK.LevelMeter#element - The main DIV container. + * Has class toolkit-level-meter. + */ + TK.add_class(this.element, "toolkit-level-meter"); + + var O = this.options; + + if (O.peak === false) + O.peak = O.value; + if (O.top === false) + O.top = O.value; + if (O.bottom === false) + O.bottom = O.value; + if (O.falling < 0) + O.falling = -O.falling; + }, + + redraw: function () { + var O = this.options; + var I = this.invalid; + var E = this.element; + + if (I.show_hold) { + I.show_hold = false; + TK.toggle_class(E, "toolkit-has-hold", O.show_hold); + } + + if (I.top || I.bottom) { + /* top and bottom require a meter redraw, so lets invalidate + * value */ + I.top = I.bottom = false; + I.value = true; + } + + if (I.base) + I.value = true; + + TK.MeterBase.prototype.redraw.call(this); + + if (I.clip) { + I.clip = false; + TK.toggle_class(E, "toolkit-clipping", O.clip); + } + }, + destroy: function () { + TK.MeterBase.prototype.destroy.call(this); + }, + /** + * Resets the peak label. + * + * @method TK.LevelMeter#reset_peak + * + * @emits TK.LevelMeter#resetpeak + */ + reset_peak: function () { + if (this.__pto) clearTimeout(this.__pto); + this.__pto = false; + this.set("peak", this.effective_value()); + /** + * Is fired when the peak was reset. + * + * @event TK.LevelMeter#resetpeak + */ + this.fire_event("resetpeak"); + }, + /** + * Resets the label. + * + * @method TK.LevelMeter#reset_label + * + * @emits TK.LevelMeter#resetlabel + */ + reset_label: function () { + if (this.__lto) clearTimeout(this.__lto); + this.__lto = false; + this.set("label", this.effective_value()); + /** + * Is fired when the label was reset. + * + * @event TK.LevelMeter#resetlabel + */ + this.fire_event("resetlabel"); + }, + /** + * Resets the clipping LED. + * + * @method TK.LevelMeter#reset_clip + * + * @emits TK.LevelMeter#resetclip + */ + reset_clip: function () { + if (this.__cto) clearTimeout(this.__cto); + this.__cto = false; + this.set("clip", false); + /** + * Is fired when the clipping LED was reset. + * + * @event TK.LevelMeter#resetclip + */ + this.fire_event("resetclip"); + }, + /** + * Resets the top hold. + * + * @method TK.LevelMeter#reset_top + * + * @emits TK.LevelMeter#resettop + */ + reset_top: function () { + this.set("top", this.effective_value()); + /** + * Is fired when the top hold was reset. + * + * @event TK.LevelMeter#resettop + */ + this.fire_event("resettop"); + }, + /** + * Resets the bottom hold. + * + * @method TK.LevelMeter#reset_bottom + * + * @emits TK.LevelMeter#resetbottom + */ + reset_bottom: function () { + this.set("bottom", this.effective_value()); + /** + * Is fired when the bottom hold was reset. + * + * @event TK.LevelMeter#resetbottom + */ + this.fire_event("resetbottom"); + }, + /** + * Resets all hold features. + * + * @method TK.LevelMeter#reset_all + * + * @emits TK.LevelMeter#resetpeak + * @emits TK.LevelMeter#resetlabel + * @emits TK.LevelMeter#resetclip + * @emits TK.LevelMeter#resettop + * @emits TK.LevelMeter#resetbottom + */ + reset_all: function () { + this.reset_label(); + this.reset_peak(); + this.reset_clip(); + this.reset_top(); + this.reset_bottom(); + }, + + effective_value: function() { + var O = this.options; + var falling = +O.falling; + if (O.falling <= 0) return O.value; + var value = +O.value, base = +O.base; + + var age = +this.value_time.value; + + if (!(age > 0)) age = Date.now(); + else age = +(Date.now() - age); + + var frame_length = 1000.0 / +O.falling_fps; + + if (age > O.falling_init * frame_length) { + if (value > base) { + value -= falling * (age / frame_length); + if (value < base) value = base; + } else { + value += falling * (age / frame_length); + if (value > base) value = base; + } + } + + return value; + }, + + /* + * This is an _internal_ method, which calculates the non-filled regions + * in the overlaying canvas as pixel positions. The canvas is only modified + * using this information when it has _actually_ changed. This can save a lot + * of performance in cases where the segment size is > 1 or on small devices where + * the meter has a relatively small pixel size. + */ + calculate_meter: function(to, value, i) { + var O = this.options; + var falling = +O.falling; + var base = +O.base; + value = +value; + + // this is a bit unelegant... + if (falling) { + value = this.effective_value(); + // continue animation + if (value !== base) { + this.invalid.value = true; + // request another frame + this.trigger_draw_next(); + } + } + + i = TK.MeterBase.prototype.calculate_meter.call(this, to, value, i); + + if (!O.show_hold) return i; + + // shorten things + var hold = +O.top; + var segment = O.segment|0; + var hold_size = (O.hold_size|0) * segment; + var base = +O.base; + var pos; + + if (hold > base) { + /* TODO: lets snap in set() */ + pos = this.val2px(hold)|0; + if (segment !== 1) pos = segment*(Math.round(pos/segment)|0); + + to[i++] = pos; + to[i++] = pos+hold_size; + } + + hold = +O.bottom; + + if (hold < base) { + pos = this.val2px(hold)|0; + if (segment !== 1) pos = segment*(Math.round(pos/segment)|0); + + to[i++] = pos; + to[i++] = pos+hold_size; + } + + return i; + }, + + // GETTER & SETTER + set: function (key, value) { + if (key === "value") { + var O = this.options; + var base = O.base; + + // snap will enforce clipping + value = this.snap(value); + + if (O.falling) { + var v = this.effective_value(); + if (v >= base && value >= base && value < v || + v <= base && value <= base && value > v) { + /* NOTE: we are doing a falling animation, but maybe its not running */ + if (!this.invalid.value) { + this.invalid.value = true; + this.trigger_draw(); + } + return; + } + } + if (O.auto_clip !== false && value > O.clipping && !this.has_base()) { + this.set("clip", true); + } + if (O.show_label && O.peak_label !== false && + (value > O.label && value > base || value < O.label && value < base)) { + this.set("label", value); + } + if (O.auto_peak !== false && + (value > O.peak && value > base || value < O.peak && value < base)) { + this.set("peak", value); + } + if (O.auto_hold !== false && O.show_hold && value > O.top) { + this.set("top", value); + } + if (O.auto_hold !== false && O.show_hold && value < O.bottom && this.has_base()) { + this.set("bottom", value); + } + } else if (key === "top" || key === "bottom") { + value = this.snap(value); + } + return TK.MeterBase.prototype.set.call(this, key, value); + } +}); + +/** + * @member {TK.State} TK.LevelMeter#clip - The {@link TK.State} instance for the clipping LED. + * @member {HTMLDivElement} TK.LevelMeter#clip.element - The DIV element of the clipping LED. + * Has class toolkit-clip. + */ +TK.ChildWidget(TK.LevelMeter, "clip", { + create: TK.State, + show: false, + map_options: { + clip: "state", + }, + default_options: { + "class": "toolkit-clip" + }, + toggle_class: true, +}); +/** + * @member {HTMLDivElement} TK.LevelMeter#_peak - The DIV element for the peak marker. + * Has class toolkit-peak. + */ +TK.ChildElement(TK.LevelMeter, "peak", { + show: false, + create: function() { + var peak = TK.element("div","toolkit-peak"); + peak.appendChild(TK.element("div","toolkit-peak-label")); + return peak; + }, + append: function() { + this._bar.appendChild(this._peak); + }, + toggle_class: true, + draw_options: [ "peak" ], + draw: function (O) { + if (!this._peak) return; + var n = this._peak.firstChild; + TK.set_text(n, O.format_peak(O.peak)); + if (O.peak > O.min && O.peak < O.max && O.show_peak) { + this._peak.style.display = "block"; + var pos = 0; + if (vert(O)) { + pos = O.basis - this.val2px(this.snap(O.peak)); + pos = Math.min(O.basis, pos); + this._peak.style.top = pos + "px"; + } else { + pos = this.val2px(this.snap(O.peak)); + pos = Math.min(O.basis, pos) + this._peak.style.left = pos + "px"; + } + } else { + this._peak.style.display = "none"; + } + /** + * Is fired when the peak was drawn. + * + * @event TK.LevelMeter#drawpeak + */ + this.fire_event("drawpeak"); + }, +}); +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/list.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/list.js new file mode 100644 index 0000000000..e67f18e2bf --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/list.js @@ -0,0 +1,73 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function (w, TK) { + +TK.List = TK.class({ + /** + * TK.List is a sortable {@link TK.Container} for {@TK.ListItems}s. + * the element is a UL instead of a DIV. + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {Function|Boolean} [options.sort=false] - A function + * expecting arguments A and B, returning a number <0, if A comes first and >0, + * if B comes first. 0 keeps both elements in place. Please refer to the + * compareFunction at W3Schools + * for any further information. + * + * @class TK.List + * + * @extends TK.Container + */ + _options: Object.assign(Object.create(TK.Container.prototype._options), { + sort: "function", + }), + _class: "List", + Extends: TK.Container, + + initialize: function (options) { + this.element = TK.element("ul", "toolkit-list"); + TK.Container.prototype.initialize.call(this, options); + }, + static_events: { + set_sort: function(f) { + if (typeof(f) === "function") { + var C = this.children.slice(0); + C.sort(f); + for (var i = 0; i < C.length; i++) { + this.element.appendChild(C[i].element); + } + } + }, + }, + append_child: function(w) { + TK.Container.prototype.append_child.call(this, w); + var O = this.options; + var C = this.children; + if (O.sort) { + C.sort(O.sort); + var pos = C.indexOf(w); + if (pos !== C.length - 1) + this.element.insertBefore(w.element, C[pos+1].element); + } + }, +}); + +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/listitem.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/listitem.js new file mode 100644 index 0000000000..895d3db59d --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/listitem.js @@ -0,0 +1,40 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function (w, TK) { + +TK.ListItem = TK.class({ + /** + * TK.ListItem is a member {@link TK.Container} of {@link TK.List}s. The + * element is a `LI` instead of a `DIV`. + * + * @class TK.ListItem + * + * @extends TK.Container + */ + _class: "ListItem", + Extends: TK.Container, + + initialize: function (options) { + this.element = TK.element("li", "toolkit-list-item"); + TK.Container.prototype.initialize.call(this, options); + }, +}); + +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/meterbase.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/meterbase.js new file mode 100644 index 0000000000..6aa54f215c --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/meterbase.js @@ -0,0 +1,534 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function(w, TK){ +function vert(O) { + return O.layout === "left" || O.layout === "right"; +} +function fill_interval(ctx, w, h, a, is_vertical) { + var i; + if (is_vertical) { + for (i = 0; i < a.length; i+= 2) { + ctx.fillRect(0, h - a[i+1], w, a[i+1]-a[i]); + } + } else { + for (i = 0; i < a.length; i+= 2) { + ctx.fillRect(a[i], 0, a[i+1]-a[i], h); + } + } +} +function clear_interval(ctx, w, h, a, is_vertical) { + var i; + if (is_vertical) { + for (i = 0; i < a.length; i+= 2) { + ctx.clearRect(0, h - a[i+1], w, a[i+1]-a[i]); + } + } else { + for (i = 0; i < a.length; i+= 2) { + ctx.clearRect(a[i], 0, a[i+1]-a[i], h); + } + } +} +function draw_full(ctx, w, h, a, is_vertical) { + + ctx.fillRect(0, 0, w, h); + clear_interval(ctx, w, h, a, is_vertical); +} +function make_interval(a) { + var i, tmp, again, j; + + do { + again = false; + for (i = 0; i < a.length-2; i+=2) { + if (a[i] > a[i+2]) { + tmp = a[i]; + a[i] = a[i+2]; + a[i+2] = tmp; + + tmp = a[i+1]; + a[i+1] = a[i+3]; + a[i+3] = tmp; + again = true; + } + } + } while (again); + + for (i = 0; i < a.length-2; i+= 2) { + if (a[i+1] > a[i+2]) { + if (a[i+3] > a[i+1]) { + a[i+1] = a[i+3]; + } + for (j = i+3; j < a.length; j++) a[j-1] = a[j]; + a.length = j-2; + i -=2; + continue; + } + } +} +function cmp_intervals(a, b) { + var ret = 0; + var i; + + for (i = 0; i < a.length; i+=2) { + if (a[i] === b[i]) { + if (a[i+1] === b[i+1]) continue; + ret |= (a[i+1] < b[i+1]) ? 1 : 2; + } else if (a[i+1] === b[i+1]) { + ret |= (a[i] > b[i]) ? 1 : 2; + } else return 4; + } + return ret; +} +function subtract_intervals(a, b) { + var i; + var ret = []; + + for (i = 0; i < a.length; i+=2) { + if (a[i] === b[i]) { + if (a[i+1] <= b[i+1]) continue; + ret.push(b[i+1], a[i+1]); + } else { + if (a[i] > b[i]) continue; + ret.push(a[i], b[i]); + } + } + + return ret; +} +TK.MeterBase = TK.class({ + /** + * TK.MeterBase is a base class to build different meters such as {@link TK.LevelMeter} from. + * TK.MeterBase uses {@link TK.Gradient} and contains a {@link TK.Scale} widget. + * TK.MeterBase inherits all options from {@link TK.Scale}. + * + * Note that the two options format_labels and + * scale_base have different names here. + * + * Note that level meters with high update frequencies can be very demanding when it comes + * to rendering performance. These performance requirements can be reduced by increasing the + * segment size using the segment option. Using a segment, the different level + * meter positions are reduced. This widget will take advantage of that by avoiding rendering those + * changes to the meter level, which fall into the same segment. + * + * @class TK.MeterBase + * + * @extends TK.Widget + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {String} [options.layout="left"] - A string describing the layout of the meter. + * Possible values are "left", "right", "top" and + * "bottom". "left" and "right" are vertical + * layouts, where the meter is on the left or right of the scale, respectively. Similarly, + * "top" and "bottom" are horizontal layouts in which the meter + * is at the top or the bottom, respectively. + * @property {Integer} [options.segment=1] - Segment size. Pixel positions of the meter level are + * rounded to multiples of this size. This can be used to give the level meter a LED effect and to + * reduce processor load. + * @property {Number} [options.value=0] - Level value. + * @property {Number} [options.base=false] - The base value of the meter. If set to false, + * the base will coincide with the minimum value options.min. The meter level is drawn + * starting from the base to the value. + * @property {Number} [options.label=0] - Value to be displayed on the label. + * @property {Function} [options.format_label=TK.FORMAT("%.2f")] - Function for formatting the + * label. + * @property {Boolean} [options.show_label=false] - If set to true a label is displayed. + * @property {Number} [options.title=false] - The title of the TK.MeterBase. Set to `false` to hide it. + * @property {Boolean} [options.show_scale=true] - Set to false to hide the scale. + * @property {Number|Boolean} [options.scale_base=false] - Base of the meter scale, see {@link TK.Scale} for more information. + * @property {Boolean} [options.show_labels=true] - If true, display labels on the + * scale. + * @property {Function} [options.format_labels=TK.FORMAT("%.2f")] - Function for formatting the + * scale labels. This is passed to TK.Scale as option labels. + * + */ + + _class: "MeterBase", + Extends: TK.Widget, + Implements: [TK.Gradient], + _options: Object.assign(Object.create(TK.Widget.prototype._options), + TK.Gradient.prototype._options, TK.Scale.prototype._options, { + layout: "string", + segment: "number", + value: "number", + base: "number|boolean", + min: "number", + max: "number", + label: "number", + title: "string|boolean", + show_labels: "boolean", + format_label: "function", + scale_base: "number|boolean", + format_labels: "function", + background: "string|boolean", + gradient: "object|boolean" + }), + options: { + layout: "left", + segment: 1, + value: 0, + base: false, + label: 0, + title: false, + show_labels: true, + format_label: TK.FORMAT("%.2f"), + levels: [1, 5, 10], // array of steps where to draw labels + scale_base: false, + format_labels: TK.FORMAT("%.2f"), + background: false, + gradient: false + }, + static_events: { + set_label: function(value) { + /** + * Is fired when the label changed. + * The argument is the actual label value. + * + * @event TK.MeterBase#labelchanged + * + * @param {string} label - The label of the {@link TK.MeterBase}. + */ + this.fire_event("labelchanged", value); + }, + set_title: function(value) { + /** + * Is fired when the title changed. + * The argument is the actual title. + * + * @event TK.MeterBase#titlechanged + * + * @param {string} title - The title of the {@link TK.MeterBase}. + */ + this.fire_event("titlechanged", value); + }, + set_segment: function(value) { + // what is this supposed to do? + // -> probably invalidate the value to force a redraw + this.set("value", this.options.value); + }, + set_value: function(value) { + /** + * Is fired when the value changed. + * The argument is the actual value. + * + * @event TK.MeterBase#valuechanged + * + * @param {number} value - The value of the {@link TK.MeterBase}. + */ + this.fire_event("valuechanged", value); + }, + set_base: function(value) { + if (value === false) { + var O = this.options; + O.base = value = O.min; + } + /** + * Is fired when the base value changed. + * The argument is the actual base value. + * + * @event TK.MeterBase#basechanged + * + * @param {number} base - The value of the base. + */ + this.fire_event("basechanged", value); + }, + set_layout: function () { + var O = this.options; + this.set("value", O.value); + this.set("min", O.min); + this.trigger_resize(); + }, + rangedchanged: function() { + var gradient = this.options.gradient; + if (gradient) { + this.set("gradient", gradient); + } + }, + }, + + initialize: function (options) { + var E; + TK.Widget.prototype.initialize.call(this, options); + var O = this.options; + /** + * @member {HTMLDivElement} TK.MeterBase#element - The main DIV container. + * Has class toolkit-meter-base. + */ + if (!(E = this.element)) this.element = E = TK.element("div"); + TK.add_class(E, "toolkit-meter-base"); + this.widgetize(E, false, true, true); + + this._bar = TK.element("div", "toolkit-bar"); + /** + * @member {HTMLCanvas} TK.MeterBase#_canvas - The canvas element drawing the mask. + * Has class toolkit-mask. + */ + this._canvas = document.createElement("canvas"); + TK.add_class(this._canvas, "toolkit-mask"); + + this._fillstyle = false; + + E.appendChild(this._bar); + + this._bar.appendChild(this._canvas); + + /** + * @member {HTMLDivElement} TK.MeterBase#_bar - The DIV element containing the masks + * and drawing the background. Has class toolkit-bar. + */ + this.delegate(this._bar); + this._last_meters = []; + this._current_meters = []; + + this.set("label", O.value); + this.set("base", O.base); + }, + + destroy: function () { + this._bar.remove(); + TK.Widget.prototype.destroy.call(this); + }, + redraw: function () { + var I = this.invalid; + var O = this.options; + var E = this.element; + + if (this._fillstyle === false) { + this._canvas.style.removeProperty("background-color"); + TK.S.add(function() { + this._fillstyle = TK.get_style(this._canvas, "background-color"); + TK.S.add(function() { + this._canvas.getContext("2d").fillStyle = this._fillstyle; + this._canvas.style.setProperty("background-color", "transparent", "important"); + this.trigger_draw(); + }.bind(this), 3); + }.bind(this), 2); + } + + if (I.reverse) { + I.reverse = false; + TK.toggle_class(E, "toolkit-reverse", O.reverse); + } + if (I.gradient || I.background) { + I.gradient = I.background = false; + this.draw_gradient(this._bar, O.gradient, O.background); + } + + TK.Widget.prototype.redraw.call(this); + + if (I.layout) { + I.layout = false; + TK.remove_class(E, "toolkit-vertical", + "toolkit-horizontal", "toolkit-left", + "toolkit-right", "toolkit-top", "toolkit-bottom"); + var scale = this.scale ? this.scale.element : null; + var bar = this._bar; + switch (O.layout) { + case "left": + TK.add_class(E, "toolkit-vertical", "toolkit-left"); + if (scale) TK.insert_after(scale, bar); + break; + case "right": + TK.add_class(E, "toolkit-vertical", "toolkit-right"); + if (scale) TK.insert_after(bar, scale); + break; + case "top": + TK.add_class(E, "toolkit-horizontal", "toolkit-top"); + if (scale) TK.insert_after(scale, bar); + break; + case "bottom": + TK.add_class(E, "toolkit-horizontal", "toolkit-bottom"); + if (scale) TK.insert_after(bar, scale); + break; + default: + throw("unsupported layout"); + } + } + + if (this._fillstyle === false) return; + + if (I.basis && O._height > 0 && O._width > 0) { + this._canvas.setAttribute("height", Math.round(O._height)); + this._canvas.setAttribute("width", Math.round(O._width)); + /* FIXME: I am not sure why this is even necessary */ + this._canvas.style.width = O._width + "px"; + this._canvas.style.height = O._height + "px"; + this._canvas.getContext("2d").fillStyle = this._fillstyle; + } + + if (I.value && O.show_label) { + this.label.set("label", O.format_label(O.value)); + } + + if (I.value || I.basis || I.min || I.max) { + I.basis = I.value = I.min = I.max = false; + this.draw_meter(); + } + }, + + resize: function() { + var O = this.options; + TK.Widget.prototype.resize.call(this); + var w = TK.inner_width(this._bar); + var h = TK.inner_height(this._bar); + this.set("_width", w); + this.set("_height", h); + var i = vert(O) ? h : w; + this.set("basis", i); + this._last_meters.length = 0; + this._fillstyle = false; + }, + + calculate_meter: function(to, value, i) { + var O = this.options; + // Set the mask elements according to options.value to show a value in + // the meter bar + var base = O.base; + var segment = O.segment|0; + var reverse = !!O.reverse; + var size = O.basis|0; + + /* At this point the whole meter bar is filled. We now want + * to clear the area between base and value. + */ + + /* canvas coordinates are reversed */ + var v1 = this.val2px(base)|0; + var v2 = this.val2px(value)|0; + + if (segment !== 1) v2 = segment*(Math.round(v2/segment)|0); + + if (v2 < v1) { + to[i++] = v2; + to[i++] = v1; + } else { + to[i++] = v1; + to[i++] = v2; + } + + return i; + }, + + draw_meter: function () { + var O = this.options; + var w = Math.round(O._width); + var h = Math.round(O._height); + var i, j; + + if (!(w > 0 && h > 0)) return; + + var a = this._current_meters; + var tmp = this._last_meters; + + var i = this.calculate_meter(a, O.value, 0); + if (i < a.length) a.length = i; + make_interval(a); + + this._last_meters = a; + this._current_meters = tmp; + + var diff; + + if (tmp.length === a.length) { + diff = cmp_intervals(tmp, a)|0; + } else diff = 4; + + if (!diff) return; + + // FIXME: this is currently broken for some reason + if (diff == 1) + diff = 4; + + var ctx = this._canvas.getContext("2d"); + ctx.fillStyle = this._fillstyle; + var is_vertical = vert(O); + + if (diff === 1) { + /* a - tmp is non-empty */ + clear_interval(ctx, w, h, subtract_intervals(a, tmp), is_vertical); + return; + } + if (diff === 2) { + /* tmp - a is non-empty */ + fill_interval(ctx, w, h, subtract_intervals(tmp, a), is_vertical); + return; + } + + draw_full(ctx, w, h, a, is_vertical); + }, + + // HELPERS & STUFF + _val2seg: function (val) { + // rounds values to fit in the segments size + // always returns values without taking options.reverse into account + var s = +this.val2px(this.snap(val)); + s -= s % +this.options.segment; + if (this.options.reverse) + s = +this.options.basis - s; + return s; + }, + + has_base: function() { + var O = this.options; + return O.base > O.min; + }, + +}); +/** + * @member {TK.Scale} TK.MeterBase#scale - The {@link TK.Scale} of the meter. + */ +TK.ChildWidget(TK.MeterBase, "scale", { + create: TK.Scale, + map_options: { + format_labels: "labels", + scale_base: "base", + }, + inherit_options: true, + show: true, + toggle_class: true, + static_events: { + set: function(key, value) { + var p = this.parent; + if (p) + p.fire_event("scalechanged", key, value); + }, + }, +}); +/** + * @member {TK.Label} TK.MeterBase#title - The {@link TK.Label} displaying the title. + * Has class toolkit-title. + */ +TK.ChildWidget(TK.MeterBase, "title", { + create: TK.Label, + show: false, + option: "title", + default_options: { "class" : "toolkit-title" }, + map_options: { "title" : "label" }, + toggle_class: true, +}); +/** + * @member {TK.Label} TK.MeterBase#label - The {@link TK.Label} displaying the label. + */ +TK.ChildWidget(TK.MeterBase, "label", { + create: TK.Label, + show: false, + default_options: { "class" : "toolkit-value" }, + toggle_class: true, +}); +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/multimeter.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/multimeter.js new file mode 100644 index 0000000000..2c0c1646cb --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/multimeter.js @@ -0,0 +1,247 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function(w, TK){ + +function add_meters (cnt, options) { + for (var i = 0; i < cnt; i++) + this.add_meter(options); +} +function add_meter (options) { + var l = this.meters.length; + var O = options; + var opt = extract_child_options(O, l); + var m = new TK.LevelMeter(opt); + + this.meters.push(m); + this.append_child(m); +} +function remove_meter (meter) { + /* meter can be int or meter instance */ + var I = this.invalid; + var M = this.meters; + + var m = -1; + if (typeof meter == "number") { + m = meter; + } else { + for (var i = 0; i < M.length; i++) { + if (M[i] == meter) { + m = i; + break; + } + } + } + if (m < 0 || m > M.length - 1) return; + this.remove_child(M[m]); + M[m].set("container", null); + // TODO: no destroy function in levelmeter at this point? + //this.meters[m].destroy(); + M = M.splice(m, 1); +} + + +TK.MultiMeter = TK.class({ + /** + * TK.MultiMeter is a collection of {@link TK.LevelMeter}s to show levels of channels + * containing multiple audio streams. It offers all options of {@link TK.LevelMeter} and + * {@link TK.MeterBase} which are passed to all instantiated level meters. + * + * @class TK.MultiMeter + * + * @extends TK.Container + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {Number} [options.count=2] - The amount of level meters. + * @property {String} [options.title=""] - The title of the multi meter. Set to `false` to hide the title from the DOM. + * @property {Array} [options.titles=["L", "R"]] - An Array containing titles for the level meters. Their order is the same as the meters. + * @property {Array} [options.values=[]] - An Array containing values for the level meters. Their order is the same as the meters. + * @property {Array} [options.labels=[]] - An Array containing label values for the level meters. Their order is the same as the meters. + * @property {Array} [options.clips=[]] - An Array containing clippings for the level meters. Their order is the same as the meters. + * @property {Array} [options.peaks=[]] - An Array containing peak values for the level meters. Their order is the same as the meters. + * @property {Array} [options.tops=[]] - An Array containing values for top for the level meters. Their order is the same as the meters. + * @property {Array} [options.bottoms=[]] - An Array containing values for bottom for the level meters. Their order is the same as the meters. + */ + _class: "MultiMeter", + Extends: TK.Container, + + /* TODO: The following sucks cause we need to maintain it according to + LevelMeters and MeterBases options. */ + _options: Object.assign(Object.create(TK.Container.prototype._options), { + count: "int", + title: "boolean|string", + titles: "array", + layout: "string", + show_scale: "boolean", + }), + options: { + count: 2, + title: false, + titles: ["L", "R"], + layout: "left", + show_scale: true, + }, + initialize: function (options) { + TK.Container.prototype.initialize.call(this, options, true); + /** + * @member {HTMLDivElement} TK.MultiMeter#element - The main DIV container. + * Has class toolkit-multi-meter. + */ + TK.add_class(this.element, "toolkit-multi-meter"); + this.meters = []; + var O = this.options; + }, + + redraw: function () { + var O = this.options; + var I = this.invalid; + var E = this.element; + var M = this.meters; + + if (I.count) { + while (M.length > O.count) + remove_meter.call(this, M[M.length-1]); + while (M.length < O.count) + add_meter.call(this, O); + E.setAttribute("class", E.getAttribute("class").replace(/toolkit-count-[0-9]*/g, "")); + E.setAttribute("class", E.getAttribute("class").replace(/ +/g, " ")); + TK.add_class(E, "toolkit-count-" + O.count); + } + + if (I.layout || I.count) { + I.count = I.layout = false; + TK.remove_class(E, "toolkit-vertical", "toolkit-horizontal", "toolkit-left", + "toolkit-right", "toolkit-top", "toolkit-bottom"); + switch (O.layout) { + case "left": + TK.add_class(E, "toolkit-vertical", "toolkit-left"); + break; + case "right": + TK.add_class(E, "toolkit-vertical", "toolkit-right"); + break; + case "top": + TK.add_class(E, "toolkit-horizontal", "toolkit-top"); + break; + case "bottom": + TK.add_class(E, "toolkit-horizontal", "toolkit-bottom"); + break; + default: + throw("unsupported layout"); + } + switch (O.layout) { + case "top": + case "left": + for (var i = 0; i < M.length - 1; i++) + M[i].set("show_scale", false); + if (M.length) + M[this.meters.length - 1].set("show_scale", O.show_scale); + break; + case "bottom": + case "right": + for (var i = 0; i < M.length; i++) + M[i].set("show_scale", false); + if (M.length) + M[0].set("show_scale", O.show_scale); + break; + } + } + + TK.Container.prototype.redraw.call(this); + }, +}); + +/** + * @member {HTMLDivElement} TK.MultiMeter#title - The {@link TK.Label} widget displaying the meters title. + */ +TK.ChildWidget(TK.MultiMeter, "title", { + create: TK.Label, + show: false, + option: "title", + default_options: { "class" : "toolkit-title" }, + map_options: { "title" : "label" }, + toggle_class: true, +}); + + + +/* + * This could be moved into TK.ChildWidgets(), + * which could in similar ways be used in the buttonarray, + * pager, etc. + * + */ + +var mapped_options = { + titles: "title", + layout: "layout", +}; + +function map_child_option_simple(value, key) { + var M = this.meters, i; + for (i = 0; i < M.length; i++) M[i].set(key, value); +} + +function map_child_option(value, key) { + var M = this.meters, i; + if (Array.isArray(value)) { + for (i = 0; i < M.length && i < value.length; i++) M[i].set(key, value[i]); + } else { + for (i = 0; i < M.length; i++) M[i].set(key, value); + } +} + +TK.add_static_event(TK.MultiMeter, "set_titles", function(value, key) { + map_child_option.call(this, value, "title"); +}); + +for (var key in TK.object_sub(TK.LevelMeter.prototype._options, TK.Container.prototype._options)) { + if (TK.MultiMeter.prototype._options[key]) continue; + var type = TK.LevelMeter.prototype._options[key]; + if (type.search("array") !== -1) { + TK.MultiMeter.prototype._options[key] = type; + mapped_options[key] = key; + TK.add_static_event(TK.MultiMeter, "set_"+key, map_child_option_simple); + } else { + TK.MultiMeter.prototype._options[key] = "array|"+type; + mapped_options[key] = key; + TK.add_static_event(TK.MultiMeter, "set_"+key, map_child_option); + } + if (key in TK.LevelMeter.prototype.options) + TK.MultiMeter.prototype.options[key] = TK.LevelMeter.prototype.options[key]; +} + +function extract_child_options(O, i) { + var o = {}, value, type; + + for (var key in mapped_options) { + var ckey = mapped_options[key]; + if (!O.hasOwnProperty(key)) continue; + value = O[key]; + type = TK.LevelMeter.prototype._options[key] || ""; + if (Array.isArray(value) && type.search("array") === -1) { + if (i < value.length) o[ckey] = value[i]; + } else { + o[ckey] = value; + } + } + + return o; +} +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/notification.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/notification.js new file mode 100644 index 0000000000..8a594fec2b --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/notification.js @@ -0,0 +1,156 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function (w, TK) { + +function close_clicked (e) { + /** + * Is fired when the user clicks on the close button. + * + * @event TK.Notification#closeclicked + */ + this.fire_event("closeclicked"); + close.call(this.parent); +} + +function after_hide() { + TK.S.after_frame(function() { + if (this.is_destructed()) return; + this.destroy(); + }.bind(this)); +} + +function close () { + this.add_event("hide", after_hide); + this.hide(); + /** + * Is fired when the notification was removed from the DOM after the hiding animation. + * + * @event TK.Notification#closed + */ + this.fire_event("closed"); +} + +function timeout() { + this._timeout = void(0); + close.call(this); +} + +/** + * TK.Notification is a {@link TK.Container} to be used in {@link TK.Notifications}. + * + * @class TK.Notification + * + * @extends TK.Container + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {Number} [options.timeout=5000] - Time in milliseconds + * after the notification disappears automatically. + * Set to 0 for permanent notification. + * @property {String} [options.icon=false] - Show an icon. Set to + * false to hide it from the DOM. + * @property {Boolean} [options.show_close=false] - Show a close button. + */ + +TK.Notification = TK.class({ + + _class: "Notification", + Extends: TK.Container, + + _options: Object.assign(TK.Container.prototype._options, { + timeout: "number", + icon: "string", + show_close: "boolean", + }), + options: { + timeout: 5000, + icon: false, + show_close: false, + }, + + initialize: function (options) { + TK.Container.prototype.initialize.call(this, options); + var O = this.options; + /** + * @member {HTMLDivElement} TK.Notification#element - The main DIV container. + * Has class toolkit-notification. + */ + TK.add_class(this.element, "toolkit-notification"); + this._timeout = void(0); + this.set("timeout", O.timeout); + }, + redraw: function () { + var I = this.invalid; + var O = this.options; + var i = I.content; + TK.Container.prototype.redraw.call(this); + if (i && this.icon) + this.element.insertBefore(this.icon.element, this.element.firstChild); + if (i && this.close) + this.element.insertBefore(this.close.element, this.element.firstChild); + }, + + remove: close, + destroy: function() { + if (this._timeout !== void(0)) + window.clearTimeout(this._timeout); + TK.Container.prototype.destroy.call(this); + }, + set: function(key, val) { + TK.Container.prototype.set.call(this, key, val); + if (key === "timeout") { + if (this._timeout !== void(0)) + window.clearTimeout(this._timeout); + if (val > 0) + this._timeout = window.setTimeout(timeout.bind(this), val); + } + }, +}); + +/** + * @member {TK.Button} TK.Notification#close - The TK.Button for closing the notification. + */ +TK.ChildWidget(TK.Notification, "close", { + create: TK.Button, + show: false, + toggle_class: true, + static_events: { + click: close_clicked, + }, + default_options: { + "icon" : "close", + "class" : "toolkit-close", + }, +}); + +/** + * @member {TK.Icon} TK.Notification#icon - The TK.Icon widget. + */ +TK.ChildWidget(TK.Notification, "icon", { + create: TK.Icon, + show: false, + toggle_class: true, + option: "icon", + map_options: { + icon: "icon", + }, +}); + +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/notifications.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/notifications.js new file mode 100644 index 0000000000..67b600bf06 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/notifications.js @@ -0,0 +1,72 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function (w, TK) { + +/** + * TK.Notifications is a {@link TK.Container} displaying {@link TK.Notification} + * popups. + * + * @class TK.Notifications + * + * @extends TK.Container + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {String} [options.stack="end"] - Define the position a new {@link TK.Notification} + * is appended to the container, either `end` or `start`. + */ + +TK.Notifications = TK.class({ + + _class: "Notifications", + Extends: TK.Container, + + _options: Object.assign(TK.Container.prototype._options, { + stack: "string", + }), + options: { + stack: "start", + }, + + initialize: function (options) { + TK.Container.prototype.initialize.call(this, options); + TK.add_class(this.element, "toolkit-notifications"); + }, + + notify: function (options) { + /** + * Create and show a new notification. + * + * @method TK.Notifications#notify + * + * @param {Object} [options={ }] - An object containing initial options. - Options for the {@link TK.Notification} to add + * + */ + var n = new TK.Notification(options); + this.add_child(n); + if (this.options.stack == "start") + this.element.insertBefore(n.element, this.element.firstChild); + else + this.element.appendChild(n.element); + return n; + } +}); + +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/pager.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/pager.js new file mode 100644 index 0000000000..206fef2f6e --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/pager.js @@ -0,0 +1,461 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + + /** + * The useraction event is emitted when a widget gets modified by user interaction. + * The event is emitted for the option show. + * + * @event TK.Pager#useraction + * + * @param {string} name - The name of the option which was changed due to the users action + * @param {mixed} value - The new value of the option + */ + +"use strict"; +(function(w, TK){ +TK.Pager = TK.class({ + /** + * TK.Pager, also known as Notebook in other UI toolkits, provides + * multiple containers for displaying contents which are switchable + * via a {@link TK.ButtonArray}. + * + * @class TK.Pager + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {String} [options.position="top"] - The position of the ButtonArray. Can either be `top`, `right`, `left` or `bottom`. + * @property {Array} [options.pages=[]] - + * An array of objects with the following members: + * @property {String|Object} [options.pages.label=""] - A string + * used as the buttons label or an object containing options for + * a {@link TK.Button}. + * @property {TK.Container|DOMNode|String} [options.pages.content] + * - The content of the page. Can be + * either an instance of {@link TK.Container} (or derivate), + * a DOMNode or a string which gets wrapped in a new {@link TK.Container}. + * @property {Integer} [options.show=-1] - The page to show. + * + * @extends TK.Container + * + * @example + * var pager = new TK.Pager({ + * pages: [ + * { + * label: "Empty Page 1", + * content: document.createElement("span") + * }, + * { + * label: { label:"Foobar", class:"foobar" }, + * content: "

Foobar

Lorem ipsum dolor sit amet

" + * } + * ] + * }); + */ + _class: "Pager", + Extends: TK.Container, + _options: Object.assign(Object.create(TK.Container.prototype._options), { + position: "string", + direction: "string", + pages: "array", + show: "int", + resized: "boolean", + }), + options: { + position: "top", + direction: "forward", + pages: [], + show: -1, + resized: false, + }, + static_events: { + set_show: function(value) { + var page = this.current(); + + if (page) { + page.set("active", true); + this.show_child(page); + /** + * The page to show has changed. + * + * @param {TK.Container} page - The {@link TK.Container} instance of the newly selected page. + * @param {number} id - The ID of the page. + * + * @event TK.Pager#changed + */ + this.fire_event("changed", page, value); + } + }, + set_pages: function(value) { + for (var i = 0; i < this.pages.length; i++) + this.pages[i].destroy(); + this.pages = []; + this.add_pages(value); + }, + set_position: function(value) { + var badir; + if (value === "top" || value === "bottom") { + badir = "horizontal"; + } else { + badir = "vertical"; + } + this.buttonarray.set("direction", badir); + }, + }, + + initialize: function (options) { + this.pages = []; + TK.Container.prototype.initialize.call(this, options); + /** + * The main DIV element. Has the class toolkit-pager. + * + * @member TK.Pager#element + */ + /** + * @member {HTMLDivElement} TK.Pager#_buttonarray_wrapper - An internal container for layout purposes containing the #TK.ButtonArray. + * Has classes toolkit-buttonarray-wrapper and toolkit-wrapper. + */ + /** + * @member {HTMLDivElement} TK.Pager#_container_wrapper - An internal container for layout purposes containing the _clip element. + * Has classes toolkit-wrapper and toolkit-container-wrapper. + */ + /** + * @member {HTMLDivElement} TK.Pager#_clip - The clipping area containing the pages. + * Has class toolkit-clip. + */ + TK.add_class(this.element, "toolkit-pager"); + /** + * The {@link TK.ButtonArray} instance acting as the menu. + * + * @member TK.Pager#buttonarray + */ + this.buttonarray = new TK.ButtonArray({ + container: this.element, + }); + this.buttonarray.add_event("userset", function(key, value) { + this.parent.userset(key, value); + return false; + }); + + var b_o = this.buttonarray._options; + var _o = this._options; + for (var k in b_o) { + if (!b_o.hasOwnProperty(k)) continue; + var o = "buttonarray." + k; + _o[o] = b_o[k]; + this.add_event("set_" + o, (function (t,k) { + return function (v) { + t.buttonarray.set(k, v); + } + })(this, k)); + if (typeof this.options[o] !== "undefined") + this.set(o, this.options[o]); + } + /** + * @member {HTMLDivElement} TK.Pager#_clip - The clipping of the pages. + * Has class toolkit-clip. + */ + this._clip = TK.element("div", "toolkit-clip"); + this.element.appendChild(this._clip); + + this.add_child(this.buttonarray); + this.add_pages(this.options.pages); + this.set("position", this.options.position); + this.set("show", this.options.show); + }, + + redraw: function () { + TK.Container.prototype.redraw.call(this); + var O = this.options; + var I = this.invalid; + var E = this.element; + + if (I.overlap) + TK[(O.overlap ? "add_" : "remove_") + "class"](E, "toolkit-overlap"); + + if (I.direction) { + I.direction = false; + TK.remove_class(E, "toolkit-forward", "toolkit-backward"); + TK.add_class(E, "toolkit-" + O.direction); + } + + if (I.position) { + I.position = false; + TK.remove_class(E, "toolkit-top", "toolkit-right", "toolkit-bottom", + "toolkit-left", "toolkit-vertical", "toolkit-horizontal"); + switch (O.position) { + case "top": + TK.add_class(E, "toolkit-top", "toolkit-vertical"); + break; + case "bottom": + TK.add_class(E, "toolkit-bottom", "toolkit-vertical"); + break; + case "left": + TK.add_class(E, "toolkit-left", "toolkit-horizontal"); + break; + case "right": + TK.add_class(E, "toolkit-right", "toolkit-horizontal"); + break; + default: + TK.warn("Unsupported position", O.position); + } + I.layout = true; + } + + if (I.show) { + I.show = false; + for (var i = 0; i < this.pages.length; i ++) { + var page = this.pages[i]; + if (i === O.show) + page.add_class("toolkit-active"); + else + page.remove_class("toolkit-active"); + } + } + }, + + /** + * Adds an array of pages. + * + * @method TK.Pager#add_pages + * + * @param {Array} options - An Array of objects with members + * `label` and `content`. `label` is a string with the {@link TK.Button}s label or + * an object containing options for the {@link TK.Button} instance. + * `content` is either a {@link TK.Container} (or derivate) widget, + * a DOMNode (needs option `options` to be set) or a string which + * gets wrapped in a new {@link TK.Container} with options from + * argument `options`. + * + * @example + * var p = new TK.Pager(); + * p.add_pages([ + * { + * label: "Page 1", + * content: "

Page1

", + * } + * ]); + * + */ + add_pages: function (options) { + for (var i = 0; i < options.length; i++) + this.add_page(options[i].label, options[i].content); + }, + + /** + * Adds a {@link TK.Container} to the pager and a corresponding {@link TK.Button} + * to the pagers {@link TK.ButtonArray}. + * + * @method TK.Pager#add_page + * + * @param {string|Object} button - A string with the {@link TK.Button}s label or + * an object containing options for the {@link TK.Button} instance. + * @param {TK.Widget|Class|string} content - The content of the page. + * Either a {@link TK.Container} (or derivate) widget, + * a DOMNode (needs option `options` to be set) or a string which + * gets wrapped in a new {@link TK.Container} with options from + * argument `options`. + * @param {Object} [options={ }] - An object containing options for + * the {@link TK.Container} to be added as page if `content` is + * either a string or a DOMNode. + * @param {integer|undefined} position - The position to add the new + * page to. If undefined, the page is added at the end. + * @emits TK.Pager#added + */ + add_page: function (button, content, position, options) { + var p; + if (typeof button === "string") + button = {label: button}; + this.buttonarray.add_button(button, position); + + if (typeof content === "string" || TK.is_dom_node(content)) { + if (!options) options = {}; + options.content = content; + p = new TK.Container(options); + } else if (typeof content === "function") { + // assume here content is a subclass of Container + p = new content(options); + } else { + p = content; + } + + p.add_class("toolkit-page"); + p.set("container", this._clip); + + var len = this.pages.length; + + if (position >= 0 && position < len - 1) { + this.pages.splice(position, 0, p); + this._clip.insertBefore(p.element, this._clip.childNodes[position]); + } else { + position = len; + this.pages.push(p); + this._clip.appendChild(p.element); + } + /** + * A page was added to the TK.Pager. + * + * @event TK.Pager#added + * + * @param {TK.Container} page - The {@link TK.Container} which was added as a page. + */ + this.fire_event("added", p); + + this.add_child(p); + + // TODO: not always necessary + if (this.current() === p) { + this.options.show = position; + this.buttonarray.set("show", position); + p.set("active", true); + p.set("display_state", "show"); + } else { + /* do not use animation */ + p.force_hide(); + this.hide_child(p); + } + this.invalid.layout = true; + this.trigger_draw(); + return p; + }, + + /** + * Removes a page from the TK.Pager. + * + * @method TK.Pager#remove_page + * + * @param {integer|TK.Container} page - The container to remove. Either a + * position or the {@link TK.Container} widget generated by add_page. + * + * @emits TK.Pager#removed + */ + remove_page: function (page) { + if (typeof page === "object") + page = this.pages.indexOf(page); + if (page < 0 || page >= this.pages.length) + return; + this.buttonarray.remove_button(page); + if (page < this.options.show) + this.set("show", this.options.show-1); + else if (page === this.options.show) + this.set("show", this.options.show); + var p = this.pages[page]; + this.pages.splice(page, 1); + p.destroy(); + this.remove_child(p); + this.invalid.layout = true; + this.trigger_draw(); + /** + * A page was removed from the Pager + * + * @event TK.Pager#removed + * + * @param {TK.Container} page - The {@link TK.Container} which was removed. + */ + this.fire_event("removed", p); + }, + + current: function() { + /** + * Returns the currently displayed page or null. + * + * @method TK.Pager#current + */ + var n = this.options.show; + if (n >= 0 && n < this.pages.length) { + return this.pages[n]; + } + return null; + }, + + /** + * Opens the first page of the pager. Returns true if a + * first page exists, false otherwise. + * + * @method TK.Pager#first + */ + first: function() { + if (this.pages.length) { + this.set("show", 0); + return true; + } + return false; + }, + /** + * Opens the last page of the pager. Returns true if a + * last page exists, false otherwise. + * + * @method TK.Pager#last + */ + last: function() { + if (this.pages.length) { + this.set("show", this.pages.length-1); + return true; + } + return false; + }, + + /** + * Opens the next page of the pager. Returns true if a + * next page exists, false otherwise. + * + * @method TK.Pager#next + */ + next: function() { + var c = this.options.show; + return this.set("show", c+1) !== c; + }, + /** + * Opens the previous page of the pager. Returns true if a + * previous page exists, false otherwise. + * + * @method TK.Pager#prev + */ + prev: function() { + var c = this.options.show; + return this.set("show", c-1) !== c; + }, + + set: function (key, value) { + var page; + if (key === "show") { + if (value < 0) value = 0; + else if (value >= this.pages.length) value = this.pages.length - 1; + + if (value === this.options.show) return value; + if (value > this.options.show) { + this.set("direction", "forward"); + } else { + this.set("direction", "backward"); + } + page = this.current(); + if (page) { + this.hide_child(page); + page.set("active", false); + } + + this.buttonarray.set("show", value); + } + return TK.Container.prototype.set.call(this, key, value); + }, + get: function (key) { + if (key === "pages") return this.pages; + return TK.Container.prototype.get.call(this, key); + } +}); +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/responsehandler.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/responsehandler.js new file mode 100644 index 0000000000..656af6643a --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/responsehandler.js @@ -0,0 +1,51 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +"use strict"; +(function(w, TK){ + +TK.ResponseHandler = TK.class({ + /** + * TK.ResponseHandler is a {@link TK.FrequencyResponse} adding some {@link TK.ResponseHandle}s. It is + * meant as a universal user interface for equalizers and the like. + * + * This class is deprecated since all relevant functionality went into + * the base class TK.Graph. Use {@link TK.FrequencyResponse} instead. + * + * @class TK.ResponseHandler + * + * @extends TK.FrequencyResponse + */ + _class: "ResponseHandler", + Extends: TK.FrequencyResponse, + initialize: function (options) { + TK.FrequencyResponse.prototype.initialize.call(this, options); + /** + * @member {HTMLDivElement} TK.ResponseHandler#element - The main DIV container. + * Has class toolkit-response-handler. + */ + TK.add_class(this.element, "toolkit-response-handler"); + /** + * @member {SVGImage} TK.ResponseHandler#_handles - An SVG group element containing all {@link TK.ResponseHandle} graphics. + * Has class toolkit-response-handles. + */ + TK.add_class(this._handles, "toolkit-response-handles"); + }, +}); +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/root.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/root.js new file mode 100644 index 0000000000..f585ef1304 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/root.js @@ -0,0 +1,76 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function(w, TK){ +function visibility_change() { + if (document.hidden) { + this.disable_draw(); + } else { + this.enable_draw(); + } +} +function resized() { + if (!this.resize_event) { + this.resize_event = true; + this.trigger_resize(); + } +} +/** + * TK.Root is used to force a resize on all its child widgets + * as soon as the browser window is resized. It also toggles drawing + * of its children depending on the state of visibility of the + * browser tab or browser window. + * + * @extends TK.Container + * + * @class TK.Root + */ +TK.Root = TK.class({ + Extends: TK.Container, + _class: "Root", + _options: Object.create(TK.Container.prototype._options), + static_events: { + initialized: function () { + window.addEventListener("resize", this._resize_cb); + document.addEventListener("visibilitychange", this._visibility_cb, false); + this.enable_draw(); + }, + destroy: function() { + window.removeEventListener("resize", this._resize_cb); + document.removeEventListener("visibilitychange", this._visibility_cb) + this._resize_cb = this._visibility_cb = null; + }, + redraw: function() { + if (this.resize_event) + this.resize_event = false; + }, + }, + initialize: function (options) { + TK.Container.prototype.initialize.call(this, options); + /** + * @member {HTMLDivElement} TK.Root#element - The main DIV container. + * Has class toolkit-root. + */ + TK.add_class(this.element, "toolkit-root"); + this._resize_cb = resized.bind(this); + this._visibility_cb = visibility_change.bind(this); + this.resize_event = false; + }, +}); +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/select.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/select.js new file mode 100644 index 0000000000..a5f41f1963 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/select.js @@ -0,0 +1,739 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + + /** + * The useraction event is emitted when a widget gets modified by user interaction. + * The event is emitted for the options selected and value. + * + * @event TK.Select#useraction + * + * @param {string} name - The name of the option which was changed due to the users action + * @param {mixed} value - The new value of the option + */ + +"use strict"; +(function(w, TK){ + +function hide_list() { + this.__transition = false; + this.__timeout = false; + if (!this.__open) { + this._list.remove(); + } else { + document.addEventListener("touchstart", this._global_touch_start); + document.addEventListener("mousedown", this._global_touch_start); + } +} +function show_list(show) { + if (show) { + var ew = TK.outer_width(this.element, true); + document.body.appendChild(this._list); + var cw = TK.width(); + var ch = TK.height(); + var sx = TK.scroll_left(); + var sy = TK.scroll_top(); + TK.set_styles(this._list, { + "opacity": "0", + "maxHeight": ch+"px", + "maxWidth": cw+"px", + "minWidth": ew+"px" + }); + var lw = TK.outer_width(this._list, true); + var lh = TK.outer_height(this._list, true); + TK.set_styles(this._list, { + "top": Math.min(TK.position_top(this.element) + TK.outer_height(this.element, true), ch + sy - lh) + "px", + "left": Math.min(TK.position_left(this.element), cw + sx - lw) + "px", + }); + } else { + document.removeEventListener("touchstart", this._global_touch_start); + document.removeEventListener("mousedown", this._global_touch_start); + } + TK.set_style(this._list, "opacity", show ? "1" : "0"); + this.__transition = true; + this.__open = show; + if (this.__timeout !== false) window.clearTimeout(this.__timeout); + var dur = TK.get_duration(this._list); + this.__timeout = window.setTimeout(hide_list.bind(this), dur); +} + +function low_remove_entry(entry) { + var li = entry.element; + var entries = this.entries; + var id = entries.indexOf(entry); + + if (id === -1) throw new Error("Entry removed twice."); + + // remove from DOM + if (li.parentElement == this._list) + this._list.removeChild(li); + // remove from list + entries.splice(id, 1); + // selection + var sel = this.options.selected; + if (sel !== false) { + if (sel > id) { + this.options.selected --; + } else if (sel === id) { + this.options.selected = false; + this.set("label", ""); + } + } + this.invalid.entries = true; + this.select(this.options.selected); + /** + * Is fired when a new entry is added to the list. + * + * @event TK.Select.entryremoved + * + * @param {Object} entry - An object containing the members title and value. + */ + this.fire_event("entryremoved", entry); +} + +TK.Select = TK.class({ + /** + * TK.Select provides a {@link TK.Button} with a select list to choose from + * a list of {@TK.SelectEntry}. + * + * @class TK.Select + * + * @extends TK.Button + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {Integer|Boolean} [options.selected=false] - The index of the selected {@TK.SelectEntry}. + * Set to `false` to unselect any already selected entries. + * @property {mixed} [options.value] - The value of the selected entry. + * @property {Boolean} [options.auto_size=true] - If `true`, the TK.Select is + * auto-sized to be as wide as the widest {@TK.SelectEntry}. + * @property {Array} [options.entries=[]] - The list of {@TK.SelectEntry}. Each member is an + * object with the two properties title and value, a string used + * as label for constructing a {@TK.SelectEntry} or an instance of {@TK.SelectEntry}. + * + */ + _class: "Select", + Extends: TK.Button, + _options: Object.assign(Object.create(TK.Button.prototype._options), { + entries: "array", + selected: "int", + value: "mixed", + auto_size: "boolean", + show_list: "boolean", + sort: "function", + resized: "boolean", + }), + options: { + entries: [], // A list of strings or objects {title: "Title", value: 1} or SelectEntry instance + selected: false, + value: false, + auto_size: true, + show_list: false, + icon: "arrowdown", + }, + static_events: { + click: function(e) { this.set("show_list", !this.options.show_list); }, + "set_show_list": function (v) {this.set("icon", (v ? "arrowup" : "arrowdown"));}, + }, + initialize: function (options) { + this.__open = false; + + this.__timeout = -1; + + /** + * @member {Array} TK.Select#entries - An array containing all entry objects with members title and value. + */ + this.entries = []; + this._active = null; + TK.Button.prototype.initialize.call(this, options); + /** + * @member {HTMLDivElement} TK.Select#element - The main DIV container. + * Has class toolkit-select. + */ + TK.add_class(this.element, "toolkit-select"); + + /** + * @member {HTMLListElement} TK.Select#_list - A HTML list for displaying the entry titles. + * Has class toolkit-select-list. + */ + this._list = TK.element("ul", "toolkit-select-list"); + this._global_touch_start = function (e) { + if (this.__open && !this.__transition && + !this._list.contains(e.target) && + !this.element.contains(e.target)) { + + this.show_list(false); + } + }.bind(this); + var sel = this.options.selected; + var val = this.options.value; + this.set("entries", this.options.entries); + if (sel === false && val !== false) { + this.set("value", val); + } else { + this.set("selected", sel); + } + }, + destroy: function () { + this.clear(); + this._list.remove(); + TK.Button.prototype.destroy.call(this); + }, + + /** + * Show or hide the select list + * + * @method TK.Select#show_list + * + * @param {boolean} show - `true` to show and `false` to hide the list + * of {@link TK.SelectEntry}. + */ + show_list: function (s) { + this.set("show_list", !!s); + }, + + /** + * Select a {@link TK.SelectEntry} by its index. + * + * @method TK.Select#select + * + * @param {Integer} index - The index of the {@link TK.SelectEntry} to select. + */ + select: function (id) { + this.set("selected", id); + }, + /** + * Select a {@link TK.SelectEntry} by its value. + * + * @method TK.Select#select_value + * + * @param {mixed} value - The value of the {@link TK.SelectEntry} to select. + */ + select_value: function (value) { + var id = this.index_by_value.call(this, value); + this.set("selected", id); + }, + /** + * Select a {@link TK.SelectEntry} by its title. + * + * @method TK.Select#select_title + * + * @param {mixed} title - The title of the {@link TK.SelectEntry} to select. + */ + select_title: function (title) { + var id = this.index_by_title.call(this, title); + this.set("selected", id); + }, + /** + * Replaces the list of {@link TK.SelectEntry} to select from with an entirely new one. + * + * @method TK.Select#set_entries + * + * @param {Array} entries - An array of {@link TK.SelectEntry} to set as the new list to select from. + * Please refer to {@link TK.Select#add_entry} for more details. + */ + set_entries: function (entries) { + // Replace all entries with a new options list + this.clear(); + this.add_entries(entries); + this.select(this.index_by_value.call(this, this.options.value)); + }, + /** + * Adds new {@link TK.SelectEntry} to the end of the list to select from. + * + * @method TK.Select#add_entries + * + * @param {Array} entries - An array of {@link TK.SelectEntry} to add to the end of the list + * of {@link TK.SelectEntry} to select from. Please refer to {@link TK.Select#add_entry} + * for more details. + */ + add_entries: function (entries) { + for (var i = 0; i < entries.length; i++) + this.add_entry(entries[i]); + }, + /** + * Adds a single {@link TK.SelectEntry} to the end of the list. + * + * @method TK.Select#add_entry + * + * @param {mixed} entry - A string to be displayed and used as the value, + * an object with members title and value + * or an instance of {@link TK.SelectEntry}. + * + * @emits TK.Select.entryadded + */ + add_entry: function (ent) { + var O = this.options; + var entry; + var entries = this.entries; + + if (TK.SelectEntry.prototype.isPrototypeOf(ent)) { + entry = ent; + } else { + entry = new TK.SelectEntry({ + value: (typeof ent === "string") ? ent : ent.value, + title: (typeof ent === "string") + ? ent : (ent.title !== void(0)) + ? ent.title : ent.value.toString() + }); + } + this.add_child(entry); + entries.push(entry); + entry.set("container", this._list) + + var id; + + if (O.sort) { + entries.sort(O.sort); + id = entries.indexOf(entry); + if (id !== entries.length - 1) + this._list.insertBefore(entry.element, entries[id+1].element); + } else { + id = entries.length - 1; + } + + this.invalid.entries = true; + + if (this.options.selected === id) { + this.invalid.selected = true; + this.trigger_draw(); + } else if (this.options.selected > id) { + this.set("selected", this.options.selected+1); + } else { + this.trigger_draw(); + } + /** + * Is fired when a new {@link TK.SelectEntry} is added to the list. + * + * @event TK.Select#entryadded + * + * @param {TK.SelectEntry} entry - A new {@link TK.SelectEntry}. + */ + this.fire_event("entryadded", entry); + }, + /** + * Remove a {@link TK.SelectEntry} from the list by its index. + * + * @method TK.Select#remove_id + * + * @param {Integer} index - The index of the {@link TK.SelectEntry} to be removed from the list. + * + * @emits TK.Select#entryremoved + */ + remove_index: function (index) { + var entry = this.entries[index]; + this.remove_child(entry); + }, + /** + * Remove a {@link TK.SelectEntry} from the list by its value. + * + * @method TK.Select#remove_value + * + * @param {mixed} value - The value of the {@link TK.SelectEntry} to be removed from the list. + * + * @emits TK.Select#entryremoved + */ + remove_value: function (val) { + this.remove_id(this.index_by_value.call(this, val)); + }, + /** + * Remove an entry from the list by its title. + * + * @method TK.Select#remove_title + * + * @param {string} title - The title of the entry to be removed from the list. + * + * @emits TK.Select#entryremoved + */ + remove_title: function (title) { + this.remove_id(this.index_by_title.call(this, title)); + }, + /** + * Remove an entry from the list. + * + * @method TK.Select#remove_entry + * + * @param {TK.SelectEntry} entry - The {@link TK.SelectEntry} to be removed from the list. + * + * @emits TK.Select#entryremoved + */ + remove_entry: function (entry) { + this.remove_child(entry); + }, + remove_entries: function (a) { + for (var i = 0; i < a.length; i++) + this.remove_entry(a[i]); + }, + remove_child: function(child) { + TK.Button.prototype.remove_child.call(this, child); + if (TK.SelectEntry.prototype.isPrototypeOf(child)) { + low_remove_entry.call(this, child); + } + }, + /** + * Get the index of a {@link TK.SelectEntry} by its value. + * + * @method TK.Select#index_by_value + * + * @param {Mixed} value - The value of the {@link TK.SelectEntry}. + * + * @returns {Integer|Boolean} The index of the entry or `false`. + */ + index_by_value: function (val) { + var entries = this.entries; + for (var i = 0; i < entries.length; i++) { + if (entries[i].options.value === val) + return i; + } + return false; + }, + /** + * Get the index of a {@link TK.SelectEntry} by its title/label. + * + * @method TK.Select#index_by_title + * + * @param {String} title - The title/label of the {@link TK.SelectEntry}. + * + * @returns {Integer|Boolean} The index of the entry or `false`. + */ + index_by_title: function (title) { + var entries = this.entries; + for (var i = 0; i < entries.length; i++) { + if (entries[i].options.title === title) + return i; + } + return false; + }, + /** + * Get the index of a {@link TK.SelectEntry} by the {@link TK.SelectEntry} itself. + * + * @method TK.Select#index_by_entry + * + * @param {TK.SelectEntry} entry - The {@link TK.SelectEntry}. + * + * @returns {Integer|Boolean} The index of the entry or `false`. + */ + index_by_entry: function (entry) { + var pos = this.entries.indexOf(entry); + return pos === -1 ? false : pos; + }, + /** + * Get a {@link TK.SelectEntry} by its value. + * + * @method TK.Select#entry_by_value + * + * @param {Mixed} value - The value of the {@link TK.SelectEntry}. + * + * @returns {TK.SelectEntry|False} The {@link TK.SelectEntry} or `false`. + */ + entry_by_value: function (val) { + var entries = this.entries; + for (var i = 0; i < entries.length; i++) { + if (entries[i].options.value === val) + return entries[i]; + } + return false; + }, + /** + * Get a {@link TK.SelectEntry} by its title/label. + * + * @method TK.Select#entry_by_title + * + * @param {String} title - The title of the {@link TK.SelectEntry}. + * + * @returns {TK.SelectEntry|Boolean} The {@link TK.SelectEntry} or `false`. + */ + entry_by_title: function (title) { + var entries = this.entries; + for (var i = 0; i < entries.length; i++) { + if (entries[i].options.title === title) + return entries[i]; + } + return false; + }, + /** + * Get a {@link TK.SelectEntry} by its index. + * + * @method TK.Select#entry_by_index + * + * @param {Integer} index - The index of the {@link TK.SelectEntry}. + * + * @returns {TK.SelectEntry|Boolean} The {@link TK.SelectEntry} or `false`. + */ + entry_by_index: function (index) { + if (index >= 0 && index < entries.length && entries[index]) + return entries[i]; + return false; + }, + /** + * Get a value by its {@link TK.SelectEntry} index. + * + * @method TK.Select#value_by_index + * + * @param {Integer} index - The index of the {@link TK.SelectEntry}. + * + * @returns {Mixed|Boolean} The value of the {@link TK.SelectEntry} or `false`. + */ + value_by_index: function(index) { + var entries = this.entries; + if (index >= 0 && index < entries.length && entries[index]) { + return entries[index].options.value; + } + return false; + }, + /** + * Get the value of a {@link TK.SelectEntry}. + * + * @method TK.Select#value_by_entry + * + * @param {TK.SelectEntry} entry - The {@link TK.SelectEntry}. + * + * @returns {mixed} The value of the {@link TK.SelectEntry}. + */ + value_by_entry: function(entry) { + return entry.options.value; + }, + /** + * Get the value of a {@link TK.SelectEntry} by its title/label. + * + * @method TK.Select#value_by_title + * + * @param {String} title - The title of the {@link TK.SelectEntry}. + * + * @returns {Mixed|Boolean} The value of the {@link TK.SelectEntry} or `false`. + */ + value_by_title: function (title) { + var entries = this.entries; + for (var i = 0; i < entries.length; i++) { + if (entries[i].options.title === title) + return entries[i].options.value; + } + return false; + }, + /** + * Remove all {@link TK.SelectEntry} from the list. + * + * @method TK.Select#clear + * + * @emits TK.Select#cleared + */ + clear: function () { + TK.empty(this._list); + this.select(false); + var entries = this.entries.slice(0); + for (var i = 0; i < entries.length; i++) { + this.remove_child(entries[i]); + } + /** + * Is fired when the list is cleared. + * + * @event TK.Select.cleared + */ + this.fire_event("cleared"); + }, + + redraw: function() { + TK.Button.prototype.redraw.call(this); + + var I = this.invalid; + var O = this.options; + var E = this.element; + + if (I.selected || I.value) { + I.selected = I.value = false; + if (this._active) { + TK.remove_class(this._active, "toolkit-active"); + } + var entry = this.entries[O.selected]; + + if (entry) { + this._active = entry.element; + TK.add_class(entry.element, "toolkit-active"); + } else { + this._active = null; + } + } + + if (I.validate("entries", "auto_size")) { + + I.show_list = true; + + var L; + + if (O.auto_size && (L = this._label)) { + var width = 0; + E.style.width = "auto"; + var orig_content = document.createDocumentFragment(); + while (L.firstChild) orig_content.appendChild(L.firstChild); + var entries = this.entries; + for (var i = 0; i < entries.length; i++) { + L.appendChild(document.createTextNode(entries[i].options.title)); + L.appendChild(document.createElement("BR")); + } + TK.S.add(function() { + width = TK.outer_width(E, true); + TK.S.add(function() { + while (L.firstChild) L.removeChild(L.firstChild); + L.appendChild(orig_content); + TK.outer_width(E, true, width); + }, 1); + }); + } + } + + if (I.validate("show_list", "resized")) { + show_list.call(this, O.show_list); + } + }, + /** + * Get the currently selected {@link TK.SelectEntry}. + * + * @method TK.Select#current + * + * @returns {TK.SelectEntry|Boolean} The currently selected {@link TK.SelectEntry} or `false`. + */ + current: function() { + if (this.options.selected !== false) + return this.entries[this.options.selected]; + return false; + }, + /** + * Get the currently selected {@link TK.SelectEntry}'s index. Just for the sake of completeness, this + * function abstracts `options.selected`. + * + * @method TK.Select#current_index + * + * @returns {Integer|Boolean} The index of the currently selected {@link TK.SelectEntry} or `false`. + */ + current_index: function() { + return this.options.selected; + }, + /** + * Get the currently selected {@link TK.SelectEntry}'s value. + * + * @method TK.Select#current_value + * + * @returns {Mixed|Boolean} The value of the currently selected {@link TK.SelectEntry} or `false`. + */ + current_value: function() { + var w = this.current(); + if (w) return w.get("value"); + return false; + }, + set: function (key, value) { + if (key === "value") { + this.set("selected", this.index_by_value.call(this, value)); + return this.current_value(); + } + + value = TK.Button.prototype.set.call(this, key, value); + + switch (key) { + case "selected": + var entry = this.current(); + if (entry !== false) { + TK.Button.prototype.set.call(this, "value", entry.options.value); + this.set("label", entry.options.label); + } else { + TK.Button.prototype.set.call(this, "value", void 0); + this.set("label", false); + } + break; + case "entries": + this.set_entries(value); + break; + } + return value; + } +}); + + +function on_select(e) { + var w = this.parent; + var id = w.index_by_entry(this); + var entry = this; + e.stopPropagation(); + e.preventDefault(); + + if (w.userset("selected", id) === false) return false; + w.userset("value", this.options.value); + /** + * Is fired when a selection was made by the user. The arguments + * are the value of the currently selected {@link TK.SelectEntry}, its index, its title and the {@link TK.SelectEntry} instance. + * + * @event TK.Select#select + * + * @param {mixed} value - The value of the selected entry. + * @param {number} value - The ID of the selected entry. + * @param {string} value - The title of the selected entry. + * @param {string} value - The title of the selected entry. + */ + w.fire_event("select", entry.options.value, id, entry.options.title); + w.show_list(false); + + return false; +} + +TK.SelectEntry = TK.class({ + /** + * TK.SelectEntry provides a {@link TK.Label} as an entry for {@link TK.Select}. + * + * @class TK.SelectEntry + * + * @extends TK.Label + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {String} [options.title=""] - The title of the entry. Kept for backward compatibility, deprecated, use label instead. + * @property {mixed} [options.value] - The value of the selected entry. + * + */ + _class: "SelectEntry", + Extends: TK.Label, + + _options: Object.assign(Object.create(TK.Label.prototype._options), { + value: "Mixed", + title: "String", + }), + options: { + title: "", + value: null + }, + initialize: function (options) { + var E = this.element = TK.element("li", "toolkit-option"); + TK.Label.prototype.initialize.call(this, options); + this.set("title", this.options.title); + }, + static_events: { + touchstart: on_select, + mousedown: on_select, + }, + set: function (key, value) { + switch (key) { + case "title": + this.set("label", value); + break; + case "label": + this.options.title = value; + break; + } + return TK.Label.prototype.set.call(this, key, value); + } +}); + +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/slider.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/slider.js new file mode 100644 index 0000000000..c046b7dabb --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/slider.js @@ -0,0 +1,207 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +/** + * The useraction event is emitted when a widget gets modified by user interaction. + * The event is emitted for the option value. + * + * @event TK.Slider#useraction + * + * @param {string} name - The name of the option which was changed due to the users action + * @param {mixed} value - The new value of the option + */ + +"use strict"; +(function(w, TK){ +function dblclick() { + this.userset("value", this.options.reset); + /** + * Is fired when the slider receives a double click in order to reset to initial value. + * + * @event TK.Slider#doubleclick + * + * @param {number} value - The value of the widget. + */ + this.fire_event("doubleclick", this.options.value); +} + +function set_background(horiz, vert, size) { + var E = this.element; + E.style["background-position"] = "-"+horiz+"px -"+vert+"px"; + + E.style["-webkit-background-size"] = size; + E.style["-moz-background-size"] = size; + E.style["-ms-background-size"] = size; + E.style["-o-background-size"] = size; + E.style["background-size"] = size; +} +/** + * TK.Slider is a {@link TK.Widget} moving its background image + * according to its value. It can be used to show strips of + * e.g. 3D-rendered faders or knobs. It's important to set the + * width and height of the widget in CSS according to the frames in + * the background file. If alignment is `horizontal` the background image + * is as height as the widget, the width keeps the ratio intact. Overall + * width of the image should be frames * width. If alignment is `vertical` + * the background image is as wide as the widget and the height of the + * image keeps the ratio intact. The height should be height of widget + * times the amount of frames. + * TK.Slider uses {@link TK.DragValue} and {@link TK.ScrollValue} + * for setting its value. + * It inherits all options of {@link TK.DragValue}, {@link TK.Ranged} and {@link TK.Warning}. + * + * @class TK.Slider + * + * @extends TK.Widget + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {Number} [options.value=0] - The current value. + * @property {Integer} [options.frames=1] - The amount of frames contained + * in the background image. + * @property {String} [options.alignment="horizontal"] - The direction + * of the frames in the image, next to (`horizontal`) or among each other (`vertical`). + * @property {String|Booelan} [options.image=false] - The image containing all frames for the slider. + * Set to `false` to set the background image via external CSS. + * + */ +TK.Slider = TK.class({ + _class: "Slider", + Extends: TK.Widget, + Implements: [TK.Ranged, TK.Warning], + _options: Object.assign(Object.create(TK.Widget.prototype._options), + TK.Ranged.prototype._options, + TK.DragValue.prototype._options, { + value: "number", + frames: "int", + alignment: "string", + image: "string|boolean", + _width: "number", + _height: "number", + + }), + options: { + value: 0, + frames: 1, + alignment: "horizontal", + image: false, + + direction: "polar", + rotation: 45, + blind_angle: 20, + basis: 300, + }, + static_events: { + dblclick: dblclick, + }, + initialize: function (options) { + TK.Widget.prototype.initialize.call(this, options); + var E; + /** + * @member {HTMLDivElement} TK.Slider#element - The main DIV container. + * Has class toolkit-slider. + */ + if (!(E = this.element)) this.element = E = TK.element("div"); + TK.add_class(E, "toolkit-slider"); + this.widgetize(E, true, true, true); + /** + * @member {TK.DragValue} TK.Knob#drag - Instance of {@link TK.DragValue} used for + * interaction. + */ + this.drag = new TK.DragValue(this, { + node: E, + classes: E, + direction: this.options.direction, + rotation: this.options.rotation, + blind_angle: this.options.blind_angle, + }); + /** + * @member {TK.ScrollValue} TK.Knob#scroll - Instance of {@link TK.ScrollValue} used for + * interaction. + */ + this.scroll = new TK.ScrollValue(this, { + node: E, + classes: E, + }); + + if (options.reset === void(0)) + options.reset = options.value; + }, + + destroy: function () { + this.drag.destroy(); + this.scroll.destroy(); + TK.Widget.prototype.destroy.call(this); + }, + + redraw: function() { + var I = this.invalid; + var O = this.options; + var E = this.element; + + if (I.image) { + I.image = false; + if (O.image) + this.element.style["background-image"] = "url('" + O.image + "')"; + else + this.element.style["background-image"] = void 0; + I.value = true; + } + + if (I.value || I.alignment || O.frames) { + I.value = false; + I.alignment = false; + I.frames = false; + var coef = this.val2coef(O.value); + var frame = Math.round(Math.max(0, O.frames - 1) * coef); + switch (O.alignment) { + default: + TK.warn("Unknown alignment, only 'vertical' and 'horizontal' are allowed"); + break; + case "vertical": + set_background.call(this, 0, frame * O._width, "100% auto"); + break; + case "horizontal": + set_background.call(this, frame * O._height, 0, "auto 100%"); + break; + } + } + + TK.Widget.prototype.redraw.call(this); + }, + + resize: function () { + this.set("_width", TK.outer_width(this.element)); + this.set("_height", TK.outer_height(this.element)); + }, + + set: function(key, value) { + switch (key) { + case "value": + if (value > this.options.max || value < this.options.min) + this.warning(this.element); + value = this.snap(value); + break; + } + if (TK.DragValue.prototype._options[key]) + this.drag.set(key, value); + return TK.Widget.prototype.set.call(this, key, value); + }, +}); +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/sortablelist.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/sortablelist.js new file mode 100644 index 0000000000..74ed1dadf8 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/sortablelist.js @@ -0,0 +1,76 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function (w, TK) { + +/* TODO */ + +var build_dragcapture = function () { + this.dragcapture = new TK.DragCapture(this, { + state: true, + onstartcapture: function (state) { + console.log(state, "start"); + return true; + }, + onmovecapture: function (state) { + console.log(state, "move"); + }, + onstopcapture: function () { + console.log("stop"); + } + }); +} + +TK.SortableList = TK.class({ + + _class: "SortableList", + Extends: TK.List, + + _options: Object.assign(Object.create(TK.Container.prototype._options), { + sortable: "boolean", + }), + options: { + sortable: false, + item_class: TK.SortableListItem, + }, + initialize: function (options) { + TK.List.prototype.initialize.call(this, options); + this.element.add_class("toolkit-sortable-list"); + }, + add_item: function (item, pos) { + var O = this.options; + var item = TK.List.prototype.add_item.call(this, item, pos); + item.set("sortable", O.sortable); + }, + set: function (key, value) { + switch (key) { + case "sortable": + var I = this.options.items; + for (var i = 0; i < I.length; i++) + I[i].set("sortable", value); + if (value && !this.dragcapture) + build_dragcapture.call(this); + break; + } + return TK.List.prototype.set.call(this, key, value); + } +}); + + +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/sortablelistitem.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/sortablelistitem.js new file mode 100644 index 0000000000..e129f59e30 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/sortablelistitem.js @@ -0,0 +1,65 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function (w, TK) { + +var build_sorter = function () { + this.sorter = new TK.Button({"class":"toolkit-sorter",container:this.element}); + this.add_child(this.sorter); +} + +TK.SortableListItem = TK.class({ + + _class: "SortableListItem", + Extends: TK.ListItem, + + _options: Object.assign(Object.create(TK.ListItem.prototype._options), { + sortable: "boolean", + }), + options: { + sortable: false, + }, + initialize: function (options) { + TK.ListItem.prototype.initialize.call(this, options); + this.element.add_class("toolkit-sortable-list-item"); + }, + redraw: function () { + TK.ListItem.prototype.redraw.call(this); + var I = this.invalid; + var O = this.options; + if (I.sortable) { + if (O.sortable) { + if (!this.sorter) { + build_sorter.call(this); + } else { + this.element.appendChild(this.sorter.element); + } + } else { + if (this.sorter) + this.element.removeChild(this.sorter.element); + } + } + }, + set: function (key, value) { + return TK.ListItem.prototype.set.call(this, key, value); + } +}); + + +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/state.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/state.js new file mode 100644 index 0000000000..a0e96a2c07 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/state.js @@ -0,0 +1,109 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function(w, TK){ +/** + * The TK.State widget is a multi-functional adaption of a traditional LED. It + * is able to show different colors as well as on/off states. The + * "brightness" can be set seamlessly. Classes can be used to display + * different styles. TK.State extends {@link TK.Widget}. + * + * The LED effect is implemented as a DIV element, which is overlayed by + * a DIV element with class toolkit-mask. `options.state` + * changes the opacity of the mask element. + * + * @class TK.State + * + * @extends TK.Widget + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {Number} [options.state=0] - The state. To toggle between `on|off` set to `1|0`. + * Set to fractions of `1` to change "brightness", e.g. `0.5`. Values > 0 trigger setting + * the class `toolkit-state-on`, while a state of `0` results in class `toolkit-state-off`. + * @property {String|Boolean} [options.color=false] - A CSS color string for the state LED or + * `false` to set the background via external CSS. + */ +TK.State = TK.class({ + _class: "State", + Extends: TK.Widget, + _options: Object.assign(Object.create(TK.Widget.prototype._options), { + state: "number|boolean", + color: "string|boolean", + }), + options: { + state: 0, // the initial state (0 ... 1) + color: false, // the base color + }, + initialize: function (options) { + TK.Widget.prototype.initialize.call(this, options); + + var E; + /** + * @member {HTMLDivElement} TK.State#element - The main DIV container. + * Has class toolkit-state. + */ + if (!(E = this.element)) this.element = E = toolkit.element("div"); + TK.add_class(E, "toolkit-state"); + this.widgetize(E, true, true, true); + + /** + * @member {HTMLDivElement} TK.State#_mask - A DIV for masking the background. + * Has class toolkit-mask. + */ + this._mask = TK.element("div","toolkit-mask"); + + E.appendChild(this._mask); + }, + destroy: function () { + this._mask.remove(); + TK.Widget.prototype.destroy.call(this); + }, + + redraw: function() { + TK.Widget.prototype.redraw.call(this); + var I = this.invalid; + var O = this.options; + + if (I.color) { + I.color = false; + if (O.color) + this.element.style["background-color"] = O.color; + else + this.element.style["background-color"] = void 0; + } + + if (I.state) { + I.state = false; + var v = +O.state; + if (!(v >= 0)) v = 0; + if (!(v <= 1)) v = 1; + + if (!O.state) { + this.remove_class("toolkit-state-on"); + this.add_class("toolkit-state-off"); + } else { + this.remove_class("toolkit-state-off"); + this.add_class("toolkit-state-on"); + } + this._mask.style["opacity"] = "" + (1 - v); + } + } +}); +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/tag.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/tag.js new file mode 100644 index 0000000000..5f173d398e --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/tag.js @@ -0,0 +1,135 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function (w, TK) { + +function remove (e, node) { + this.fire_event("remove", node); + if (!this.options.async) + this.remove_node(node); +} + +function colorize (e) { + var that = this; + var c = new TK.ColorPickerDialog({ + autoclose: true, + hex: this.options.color, + onapply: function (rgb, hsl, hex) { + if (!that.options.async) + that.userset("color", hex); + else + that.fire_event("userset", "color", hex); + + }, + container: document.body, + }); + c.open(e.pageX, e.pageY); + c.show(); + this.colorpicker = c; + w.c = c; +} + +TK.Tag = TK.class({ + _class: "Tag", + Extends: TK.Widget, + Implements: TK.Colors, + + _options: Object.assign(Object.create(TK.Widget.prototype._options), { + color: "string|null", + tag: "string", + async: "boolean", + node_class: "constructor", + }), + options: { + color: null, + tag: "", + async: false, + node_class: TK.TagNode, + }, + initialize: function (options) { + TK.Widget.prototype.initialize.call(this, options); + this.nodes = []; + }, + destroy: function () { + var l = this.nodes.length; + for (var i = 0; i < l; i++) + this.remove_node(this.nodes[i]); + TK.Widget.prototype.destroy.call(this); + }, + + redraw: function () { + var I = this.invalid; + var O = this.options; + if (I.color) { + I.color = false; + for (var i = 0; i < this.nodes.length; i++) { + this.nodes[i].element.style.backgroundColor = O.color; + if (O.color) + this.nodes[i].element.style.color = this.rgb2gray(this.hex2rgb(O.color)) > 0.5 ? "black" : "white"; + else + this.nodes[i].element.style.color = null; + } + } + if (I.tag) { + I.tag = false; + for (var i = 0; i < this.nodes.length; i++) + this.nodes[i].children[0].textContent = O.tag; + } + TK.Widget.prototype.redraw.call(this); + }, + remove_node: function (node) { + var O = this.options; + for (var i = 0; i < this.nodes.length; i++) { + if (this.nodes[i] == node) { + node.set("container", false); + this.fire_event("noderemoved", node); + this.nodes.splice(i, 1); + node.destroy(); + return true; + } + } + }, + create_node: function (options) { + var O = this.options; + options = options || {}; + options.color = O.color; + options.tag = O.tag; + var node = new O.node_class(options, this); + node.add_event("colorize", colorize.bind(this)); + node.add_event("remove", remove.bind(this)); + this.nodes.push(node); + this.fire_event("nodecreated", node); + return node; + }, + set: function (key, value) { + switch (key) { + case "color": + for (var i = 0; i < this.nodes.length; i++) + this.nodes[i].set("color", this.options.color); + break; + case "tag": + for (var i = 0; i < this.nodes.length; i++) + this.nodes[i].set("tag", this.options.tag); + break; + } + return TK.Widget.prototype.set.call(this, key, value); + } +}); + +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/taggable.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/taggable.js new file mode 100644 index 0000000000..4ab246373e --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/taggable.js @@ -0,0 +1,155 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function (w, TK) { + +function add (e) { + this.fire_event("addtag", e); +} + +function remove (e, tagnode) { + this.fire_event("removetag", tagnode); + if (!this.options.async) + this.remove_tag(tagnode.tag, tagnode); +} + +TK.Taggable = TK.class({ + _options: { + tags: "array", + backend: "object", + add_label: "string", + show_add: "boolean", + async: "boolean", + tag_class: "object", + tag_options: "object", + }, + options: { + tags: [], + backend: false, + add_label: "✚", + show_add: true, + async: false, + tag_class: TK.Tag, + tag_options: {}, + }, + static_events: { + destroy: function() { + this.tags.destroy(); + this.add.destroy(); + }, + }, + initialize: function () { + var O = this.options; + this.taglist = []; + if (!O.backend) + O.backend = new TK.Tags({}); + + this.tags = new TK.Container({ + "class" : "toolkit-tags" + }); + this.append_child(this.tags); + + this.add = new TK.Button({ + container: this.element, + label: O.add_label, + "class": "toolkit-add", + "onclick": add.bind(this), + }); + this.append_child(this.add); + + this.add_tags(O.tags); + }, + + request_tag: function (tag, tag_class, tag_options) { + return this.options.backend.request_tag( + tag, + tag_class || this.options.tag_class, + tag_options || this.options.tag_options); + }, + add_tags: function (tags) { + for (var i = 0; i < tags.length; i++) + this.add_tag(tags[i]); + }, + add_tag: function (tag, options) { + var B = this.options.backend; + tag = B.request_tag(tag, options); + if (this.has_tag(tag)) return; + + var node = tag.create_node(options); + this.tags.append_child(node); + + node.add_event("remove", remove.bind(this)); + + var t = {tag:tag, node:node}; + this.taglist.push(t); + this.fire_event("tagadded", tag, node); + return t; + }, + has_tag: function (tag) { + tag = this.request_tag(tag); + for (var i = 0; i < this.taglist.length; i++) { + if (this.taglist[i].tag === tag) + return true; + } + return false; + }, + remove_tag: function (tag, node, purge) { + var B = this.options.backend; + tag = B.request_tag(tag); + if (!this.has_tag(tag)) return; + for (var i = 0; i < this.taglist.length; i++) { + if (this.taglist[i].tag === tag) { + this.taglist.splice(i, 1); + break; + } + } + + if (!node) { + var c = this.tags.children; + if (c) { + for (var i = 0; i < c.length; i++) { + var tagnode = c[i]; + if (tagnode.tag === tag) { + tag.remove_node(tagnode); + this.remove_child(tagnode); + break; + } + } + } + } else { + tag.remove_node(node); + } + if (purge) + B.remove_tag(tag); + this.fire_event("tagremoved", tag); + }, + empty: function () { + var T = this.taglist; + while (T.length) + this.remove_tag(T[0].tag, T[0].node); + }, + tag_to_string: function (tag) { + return this.options.backend.tag_to_string.call(this, tag); + }, + find_tag: function (tag) { + this.options.backend.find_tag.call(this, tag); + }, +}); + +})(this, TK) diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/taggablelistitem.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/taggablelistitem.js new file mode 100644 index 0000000000..79ecefcfbc --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/taggablelistitem.js @@ -0,0 +1,35 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function (w, TK) { + +TK.TaggableListItem = TK.class({ + + _class: "TaggableListItem", + Extends: TK.ListItem, + Implements: TK.Taggable, + + initialize: function (options) { + TK.ListItem.prototype.initialize.call(this, options); + TK.Taggable.prototype.initialize.call(this); + TK.add_class(this.element, "toolkit-taggable-list-item"); + } +}); + +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/taggabletreeitem.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/taggabletreeitem.js new file mode 100644 index 0000000000..0f5771bf8e --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/taggabletreeitem.js @@ -0,0 +1,35 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function (w, TK) { + +TK.TaggableTreeItem = TK.class({ + + _class: "TaggableTreeItem", + Extends: TK.TreeItem, + Implements: TK.Taggable, + + initialize: function (options) { + TK.TreeItem.prototype.initialize.call(this, options); + TK.Taggable.prototype.initialize.call(this); + TK.add_class(this.element, "toolkit-taggable-tree-item"); + }, +}); + +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/tagger.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/tagger.js new file mode 100644 index 0000000000..eae1655fec --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/tagger.js @@ -0,0 +1,99 @@ +(function (w, TK) { +"use strict" + +function keyup (e) { + if (e.keyCode != 13) return; + new_tag_from_input.call(this); +} +function addclicked (e) { + new_tag_from_input.call(this); +} +function new_tag_from_input () { + var val = this._input.value; + if (!val) return; + this._input.value = ""; + var t = false; + if (!this.options.async) + t = this.add_tag(val); + this.fire_event("newtag", val, t); + if (this.options.closenew) + this.close(); +} + +TK.Tagger = TK.class({ + + _class: "Tagger", + Extends: TK.Dialog, + Implements: TK.Taggable, + + _options: Object.assign(Object.create(TK.Dialog.prototype._options), { + closenew: "boolean", + add: "boolean", + }), + options: { + closenew: true, + visible: false, + add: true, + }, + initialize: function (options) { + TK.Dialog.prototype.initialize.call(this, options); + TK.add_class(this.element, "toolkit-tagger"); + + TK.Taggable.prototype.initialize.call(this); + this.append_child(this.tags); + this.add_event("addtag", new_tag_from_input.bind(this)); + + this.set("add", this.options.add); + }, + destroy: function (options) { + TK.Taggable.prototype.destroy.call(this); + TK.Dialog.prototype.destroy.call(this); + }, + redraw: function () { + TK.Dialog.prototype.redraw.call(this); + var I = this.invalid; + var O = this.options; + if (I.add) { + I.add = false; + if (O.add) { + if (!this._input) { + this._input = TK.element("input", "toolkit-input"); + this._input.addEventListener("keyup", keyup.bind(this), true); + this._input.type = "text"; + this._input.placeholder = "New tag"; + this.element.appendChild(this._input); + } + this.add.set("container", this.element); + TK.add_class(this.element, "toolkit-has-input"); + } else if (!O.add) { + if (this._input) { + this.element.removeChild(this._input); + this._input = null; + } + this.add.set("container", false); + TK.remove_class(this.element, "toolkit-has-input"); + } + } + }, + add_tag: function (tag, options) { + var t = TK.Taggable.prototype.add_tag.call(this, tag, options); + if (!t) return; + t.node.label.add_event("click", (function (that, tag) { + return function (e) { + that.fire_event("tagclicked", tag.tag, tag.node); + } + })(this, t)); + if (this.options.visible) + this.reposition(); + return t; + }, + remove_tag: function (tag, node, purge) { + TK.Taggable.prototype.remove_tag.call(this, tag, node, purge); + if (!this.taglist.length) + this.close(); + if (this.options.visible) + this.reposition(); + }, +}); + +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/tagnode.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/tagnode.js new file mode 100644 index 0000000000..9255fa9750 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/tagnode.js @@ -0,0 +1,93 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function (w, TK) { + +TK.TagNode = TK.class({ + + Extends: TK.Container, + Implements: TK.Colors, + + _options: Object.assign(Object.create(TK.Container.prototype._options), { + label: "string", + color: "string|null", + confirm: "boolean", + }), + options: { + label: "", + color: null, + confirm: false, + }, + + initialize: function (options, tag) { + TK.Container.prototype.initialize.call(this, options); + this.tag = tag; + this.add_class("toolkit-tag"); + + }, + + redraw: function () { + TK.Container.prototype.redraw.call(this); + var I = this.invalid; + var O = this.options; + if (I.color) { + I.color = false; + this.element.style.backgroundColor = O.color; + if (O.color) + this.element.style.color = this.rgb2bw(this.hex2rgb(O.color)); + else + this.element.style.color = null; + } + } +}); + +TK.ChildWidget(TK.TagNode, "label", { + create: TK.Label, + show: true, + map_options: { + tag: "label", + }, + toggle_class: true, +}); +TK.ChildWidget(TK.TagNode, "colorize", { + create: TK.Button, + show: false, + toggle_class: true, + static_events: { + click: function (e) { this.parent.fire_event("colorize", e); } + }, + default_options: { + class: "toolkit-colorize" + }, +}); +TK.ChildWidget(TK.TagNode, "remove", { + create: TK.ConfirmButton, + show: true, + toggle_class: true, + static_events: { + confirmed: function (e) { this.parent.fire_event("remove", e, this.parent); } + }, + default_options: { + class: "toolkit-remove", + label : "", + confirm: false, + }, +}); + +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/tags.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/tags.js new file mode 100644 index 0000000000..7c309f2a34 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/tags.js @@ -0,0 +1,87 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function (w, TK) { + +TK.Tags = TK.class({ + + Extends: TK.Widget, + + _options: Object.assign(Object.create(TK.Widget.prototype._options), { + tag_class: "object", + }), + options: { + tag_class: TK.Tag, + }, + + initialize: function (options) { + this.tags = new Map(); + this.tag_to_name = new Map(); + TK.Widget.prototype.initialize.call(this, options); + }, + tag_to_string: function (tag) { + if (typeof tag == "string") { + return tag + } else if (TK.Tag.prototype.isPrototypeOf(tag)) { + if (!tag.is_destructed()) { + return tag.options.tag; + } else { + return this.tag_to_name.get(tag); + } + } else { + return tag.tag; + } + }, + find_tag: function (tag) { + return this.tags.get(this.tag_to_string(tag)); + }, + request_tag: function (tag, options) { + var C = this.options.tag_class; + var t = this.tag_to_string(tag); + + if (this.tags.has(t)) { + tag = this.tags.get(t); + if (!tag.is_destructed()) return tag; + } + + if (typeof tag == "string") { + var o = Object.assign(options || {}, {tag:tag}); + tag = new C(o); + } else if (C.prototype.isPrototypeOf(tag)) { + tag = tag; + } else { + tag = new C(tag); + } + tag.show(); + this.tags.set(t, tag); + this.tag_to_name.set(tag, t); + return tag; + }, + remove_tag: function (tag) { + tag = this.find_tag(tag); + this.tags.delete(this.tag_to_string(tag)); + this.tag_to_name.delete(tag); + }, + empty: function() { + this.tags = new Map(); + this.tag_to_name = new Map(); + }, +}); + +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/toggle.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/toggle.js new file mode 100644 index 0000000000..ab365467d4 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/toggle.js @@ -0,0 +1,237 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function(w, TK){ + +/* Abstract toggle logic */ + +function reset_delay_to () { + window.clearTimeout(this.__delayed_to); + this.__delayed_to = -1; + this.remove_class("toolkit-delayed"); +} + +function toggle(O) { + if (this.userset("state", !O.state) === false) return; + this.fire_event("toggled", O.state); +} +function press_start() { + var O = this.options; + this.__press_start_time = Date.now(); + if (O.delay && this.__delayed_to < 0) { + this.__delayed_to = window.setTimeout((function (t) { + return function () { press_start.call(t); } + })(this), O.delay); + this.add_class("toolkit-delayed"); + return; + } + this.remove_class("toolkit-delayed"); + if (O.delay && this.__delayed_to >= 0) { + toggle.call(this, O); + } + if (O.press) toggle.call(this, O); +} +function press_end() { + var O = this.options; + if (O.delay && this.__delayed_to >= 0) { + reset_delay_to.call(this); + return; + } + var t = Date.now() - this.__press_start_time; + if ((O.toggle && (!O.press || t > O.press)) || (!O.toggle && O.press)) { + toggle.call(this, O); + } +} +function press_cancel() { + var O = this.options; + if (O.delay && this.__delayed_to >= 0) { + reset_delay_to.call(this); + return; + } + /* this is definitely not a click, its a cancel by leaving the + * button with mouse or finger while pressing */ + if (O.press) toggle.call(this, O); +} + +/* MOUSE handling */ +function mouseup(e) { + this.remove_event("mouseup", mouseup); + this.remove_event("mouseleave", mouseleave); + press_end.call(this); +} +function mouseleave(e) { + this.remove_event("mouseup", mouseup); + this.remove_event("mouseleave", mouseleave); + press_cancel.call(this); +} +function mousedown(e) { + /* only left click please */ + if (e.button) return true; + press_start.call(this); + this.add_event("mouseup", mouseup); + this.add_event("mouseleave", mouseleave); +} + +/* TOUCH handling */ +function is_current_touch(ev) { + var id = this.__touch_id; + var i; + for (i = 0; i < ev.changedTouches.length; i++) { + if (ev.changedTouches[i].identifier === id) { + return true; + } + } + + return false; +} + +function touchend(e) { + if (!is_current_touch.call(this, e)) return; + this.__touch_id = false; + e.preventDefault(); + press_end.call(this); + + this.remove_event("touchend", touchend); + this.remove_event("touchcancel", touchleave); + this.remove_event("touchleave", touchleave); +} +function touchleave(e) { + if (!is_current_touch.call(this, e)) return; + this.__touch_id = false; + e.preventDefault(); + press_cancel.call(this); + + this.remove_event("touchend", touchend); + this.remove_event("touchcancel", touchleave); + this.remove_event("touchleave", touchleave); +} +function touchstart(e) { + if (this.__touch_id !== false) return; + this.__touch_id = e.targetTouches[0].identifier; + press_start.call(this); + this.add_event("touchend", touchend); + this.add_event("touchcancel", touchleave); + this.add_event("touchleave", touchleave); + e.preventDefault(); + e.stopPropagation(); + return false; +} +function contextmenu(e) { + e.preventDefault(); + e.stopPropagation(); + return false; +} +var class_regex = /[^A-Za-z0-9_\-]/; +function is_class_name (str) { + return !class_regex.test(str); +} + +TK.Toggle = TK.class({ + /** + * A toggle button. The toggle button can either be pressed (which means that it will + * switch its state as long as it is pressed) or toggled permanently. Its behavior is + * controlled by the two options press and toggle. + * + * @class TK.Toggle + * + * @extends TK.Button + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {Boolean} [options.toggle=true] - If true, the button is toggled on click. + * @property {Integer} [options.press=0] - Controls press behavior. If options.toggle + * is false and this option is 0, the toggle button will toggle until + * released. If options.toggle is true and this option is a positive integer, it is + * interpreted as a milliseconds timeout. When pressing a button longer than this timeout, it will + * be toggled until released, otherwise it will be toggled permanently. + * @property {Integer} [options.delay=0] - Delay all actions for n milliseconds. While actions are + * delayed, the widget has class toolkit-delayed. Use to force users to press the button + * for a certain amount of time before it actually gets toggled. + * @property {String|Boolean} [options.icon_active=false] - An optional icon which is only displayed + * when the button toggle state is true. Please note that this option only works if `icon` is also set. + * @property {String|Boolean} [options.label_active=false] - An optional label which is only displayed + * when the button toggle state is true. Please note that this option only works if `label` is also set. + */ + _class: "Toggle", + Extends: TK.Button, + _options: Object.assign(Object.create(TK.Button.prototype._options), { + label_active: "string", + icon_active: "string", + press: "int", + delay: "int", + toggle: "boolean", + }), + options: { + label_active: false, + icon_active: false, + icon_inactive: false, + press: 0, + delay: 0, + toggle: true, + }, + static_events: { + mousedown: mousedown, + touchstart: touchstart, + contextmenu: contextmenu, + }, + + initialize: function (options) { + TK.Button.prototype.initialize.call(this, options); + /** + * @member {HTMLDivElement} TK.Toggle#element - The main DIV container. + * Has class toolkit-toggle. + */ + TK.add_class(this.element, "toolkit-toggle"); + this.__press_start_time = 0; + this.__touch_id = false; + this.__delayed_to = -1; + }, + + redraw: function () { + var O = this.options; + var I = this.invalid; + if (I.state) { + var tmp = (O.state && O.label_active) || O.label; + if (tmp) + this.label.set("label", tmp || ""); + tmp = (O.state && O.icon_active) || O.icon; + if (tmp) + this.icon.set("icon", tmp || ""); + } + TK.Button.prototype.redraw.call(this); + }, + /** + * Toggle the button state. + * + * @method TK.Toggle#toggle + * + * @emits TK.Toggle#toggled + */ + toggle: function () { + toggle.call(this, this.options); + /** + * Is fired when the button was toggled. + * + * @event TK.Toggle#toggled + * + * @param {boolean} state - The state of the {@link TK.Toggle}. + */ + }, +}); +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/tooltips.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/tooltips.js new file mode 100644 index 0000000000..fbc1d941d3 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/tooltips.js @@ -0,0 +1,205 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function(w, TK){ +function get_event(event) { + // return the right event if touch surface is used + // with multiple fingers + return (event.touches && event.touches.length) + ? event.touches[0] : event; +} +/** + * TK.GlobalTooltip is a small text field following the mouse. + * Due to optimization it is implemented as a table with a single cell + * containing the content. + * + * @class TK.Tooltip + */ +TK.GlobalTooltip = function() { + var overlay = TK.element("div", "toolkit-tooltip"); + var table = TK.element("div", "toolkit-table"); + var row = TK.element("div", "toolkit-row"); + var cell = TK.element("div", "toolkit-cell"); + + var tmp = row.cloneNode(); + + var spacer_tl = cell.cloneNode(); + + tmp.appendChild(spacer_tl); + tmp.appendChild(cell.cloneNode()); + tmp.appendChild(cell.cloneNode()); + + table.appendChild(tmp); + + tmp = row.cloneNode(); + + var entry = cell.cloneNode(); + entry.className += " toolkit-entry"; + + var entry_tl = cell.cloneNode(); + entry_tl.className += " toolkit-tt-container"; + entry_tl.appendChild(entry); + + tmp.appendChild(cell.cloneNode()); + tmp.appendChild(entry_tl); + tmp.appendChild(cell.cloneNode()); + + table.appendChild(tmp); + + tmp = row.cloneNode(); + + tmp.appendChild(cell.cloneNode()); + tmp.appendChild(cell.cloneNode()); + tmp.appendChild(cell.cloneNode()); + + table.appendChild(tmp); + overlay.appendChild(table); + + var tooltips = []; + + function current_callback() { + for (var i = 0; i < tooltips.length; i++) { + if (tooltips[i] && tooltips[i].length) { + return tooltips[i][0]; + } + } + } + + var ev = null; + + function redraw() { + var e = ev; + ev = null; + var current = current_callback(); + + if (!current) { + hide(); + return; + } + + var w = e.clientX; + var h = e.clientY / window.innerHeight; + + spacer_tl.style.width = w > 0 ? w + "px" : "0.01%"; + spacer_tl.style.height = h > 0 ? (h * 100).toFixed(2) + "%" : "0.01%"; + + current(e, entry); + } + + function onmove_mouse(e) { + if (!ev) { + TK.S.add(redraw, 1); + } + ev = e; + } + + function onmove_touch(e) { + onmove_mouse(get_event(e)); + } + + var hidden = false; + var num_tooltips = 0; + + /** + * Hide the tooltips + * + * @method TK.Tooltip#hide + */ + function hide() { + document.removeEventListener("mousemove", onmove_mouse); + document.removeEventListener("touchmove", onmove_touch); + overlay.style.display = "none"; + hidden = true; + } + /** + * Show the tooltips + * + * @method TK.Tooltip#show + */ + function show() { + if (!overlay.parentNode) + document.body.appendChild(overlay); + document.addEventListener("mousemove", onmove_mouse); + document.addEventListener("touchmove", onmove_touch); + overlay.style.removeProperty("display"); + hidden = false; + } + /** + * Add a new tooltip. + * + * @method TK.Tooltip#add + * + * @param {int} priority - The priority of the tooltip. States its position in the list. + * @param {Function} onmove - The function which sets the text of the tooltip while moving the mouse. + */ + function add(priority, onmove) { + if (!tooltips[priority]) tooltips[priority] = []; + tooltips[priority].push(onmove); + if (hidden) show(); + num_tooltips++; + } + /** + * Remove a tooltip. + * + * @method TK.Tooltip#remove + * + * @param {int} priority - The priority of the tooltip. + * @param {Function} onmove - The function which sets the text of the tooltip while moving the mouse. + */ + function remove(priority, onmove) { + if (!tooltips[priority]) return; + var i = tooltips[priority].indexOf(onmove); + + if (i === -1) return; + if (tooltips[priority].length === 1) tooltips[priority] = null; + else tooltips[priority].splice(i, 1); + if (--num_tooltips === 0) hide(); + } + + hide(); + + this.show = show; + this.hide = hide; + this.add = add; + this.remove = remove; + this.trigger = onmove_touch; + + /** + * @member {HTMLDivElement} TK.Tooltip#element - The overlay containing the tooltip table. + * Has class toolkit-tooltip. + */ + this.element = overlay; + /** + * @member {HTMLDivElement} TK.Tooltip#_table - The table containing the tooltips. + * Has class toolkit-table. + */ + this._table = table; + /** + * @member {HTMLDivElement} TK.Tooltip#_entry - The element containing the tooltip text. + * Has class toolkit-entry. + */ + this._entry = entry; +}; +TK.GlobalTooltip.prototype = { + destroy: function() { + this.hide(); + }, +}; +TK.tooltip = new TK.GlobalTooltip(); +})(this, TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/treeitem.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/treeitem.js new file mode 100644 index 0000000000..937c01ffdc --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/treeitem.js @@ -0,0 +1,173 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; +(function (w, TK) { + +var toggle_collapsed = function () { + set_collapsed.call(this, !TK.has_class(this.element, "toolkit-collapsed")); +} +var set_collapsed = function (c) { + this.set("collapsed", c); +} + +var reset_size = function (state) { + if (state !== "show") return; + //this.element.style.height = null; +} + +TK.TreeItem = TK.class({ + + _class: "TreeItem", + Extends: TK.ListItem, + + _options: Object.assign(Object.create(TK.Widget.prototype._options), { + collapsed: "boolean", + collapsable: "boolean", + force_collapsable: "boolean", + }), + options: { + collapsed: false, + collapsable: true, + force_collapsable: false, + }, + initialize: function (options) { + this.list = new TK.List({ + "onset_display_state": reset_size + }); + this.flex = new TK.Container({"class":"toolkit-flex"}); + + TK.ListItem.prototype.initialize.call(this, options); + TK.add_class(this.element, "toolkit-tree-item"); + + TK.ListItem.prototype.append_child.call(this, this.flex); + TK.ListItem.prototype.add_child.call(this, this.list); + this.flex.show(); + + this.collapse = new TK.Button({"class":"toolkit-collapse"}); + this.append_child(this.collapse); + this.collapse.add_event("click", toggle_collapsed.bind(this)); + + if (this.options.collapsable && this.options.collapsed) { + this.list.element.style.height = "0px"; + this.list.hide(); + } + }, + append_child: function (child) { + this.invalid._list = true; + this.trigger_resize(); + if (TK.ListItem.prototype.isPrototypeOf(child)) { + return this.list.append_child(child); + } else { + return this.flex.append_child(child); + } + }, + add_child : function(child) { + this.trigger_resize(); + if (TK.ListItem.prototype.isPrototypeOf(child)) { + return this.list.add_child(child); + } else { + return this.flex.add_child(child); + } + }, + remove_child : function(child) { + if (TK.ListItem.prototype.isPrototypeOf(child)) { + var r = this.list.remove_child(child); + return r; + } else { + this.flex.remove_child(child); + } + this.invalid._list = true; + this.trigger_resize(); + }, + redraw: function () { + TK.ListItem.prototype.redraw.call(this); + var I = this.invalid; + var O = this.options; + var E = this.element; + var F = this.flex.element; + if (I._list) { + I.collapsed = true; + if (this.list.children && this.list.children.length) { + if (this.list.element.parentElement != E) + E.appendChild(this.list.element); + this.add_class("toolkit-has-tree"); + } else { + if (this.list.element.parentElement == E) + E.removeChild(this.list.element); + this.remove_class("toolkit-has-tree"); + } + } + if (I._list || I.collapsable || I.force_collapsable) { + if ((this.list.children && this.list.children.length && O.collapsable) || O.force_collapsable) + F.appendChild(this.collapse.element); + else if (this.collapse.element.parentElement == E) + F.removeChild(this.collapse.element); + TK.toggle_class(E, "toolkit-force-collapsable", O.force_collapsable); + } + if (I.collapsed) { + I.collapsed = false; + var s = this.list.element.style; + if (O.collapsed) { + var h = this.list.element.offsetHeight; + s["height"] = h + "px"; + window.requestAnimationFrame(function () {s["height"] = "0px";}); + } else { + var list = this.list.element; + /* This is a train */ + TK.S.add(function() { + var h0 = list.offsetHeight; + var duration = TK.get_duration(list); + TK.S.add(function() { + s["height"] = "auto"; + TK.S.add(function() { + var h = list.offsetHeight; + TK.S.add(function() { + s["height"] = h0 + "px"; + TK.S.add_next(function () { + s["height"] = h + "px"; + + setTimeout(function () { + s.height = null; + }, duration); + }); + }, 1) + }); + }, 1); + }); + } + TK.toggle_class(E, "toolkit-collapsed", O.collapsed); + } + I._list = I.collapsable = I.force_collapsable = false; + }, + set: function (key, value) { + var O = this.options; + switch (key) { + case "collapsed": + if (!this.list) break; + if (!value) + this.list.show(); + else + this.list.hide(); + break; + } + return TK.ListItem.prototype.set.call(this, key, value); + } +}); + +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/value.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/value.js new file mode 100644 index 0000000000..4a955be560 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/value.js @@ -0,0 +1,262 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + + /** + * The useraction event is emitted when a widget gets modified by user interaction. + * The event is emitted for the option value. + * + * @event TK.Value#useraction + * + * @param {string} name - The name of the option which was changed due to the users action + * @param {mixed} value - The new value of the option + */ + +"use strict"; +(function(w, TK){ +function value_clicked(e) { + var O = this.options; + if (O.set === false) return; + if (this.__editing) return false; + TK.add_class(this.element, "toolkit-active"); + this._input.setAttribute("value", O.value); + this.__editing = true; + this._input.focus(); + if (O.auto_select) + this._input.setSelectionRange(0, this._input.value.length) + /** + * Is fired when the value was clicked. + * + * @event TK.Value#valueclicked + * + * @param {number} value - The value of the widget. + */ + this.fire_event("valueclicked", O.value); +} +function value_typing(e) { + var O = this.options; + if (O.set === false) return; + if (!this.__editing) return; + switch (e.keyCode) { + case 27: + // ESC + value_done.call(this); + /** + * Is fired when the ESC key was pressed while editing the value. + * + * @event TK.Value#valueescape + * + * @param {string} value - The new value of the widget. + */ + this.fire_event("valueescape", O.value); + break; + case 13: + // ENTER + this.userset("value", O.set ? O.set(this._input.value) : this._input.value); + value_done.call(this); + /** + * Is fired after the value has been set and editing has ended. + * + * @event TK.Value#valueset + * + * @param {string} value - The new value of the widget. + */ + this.fire_event("valueset", O.value); + + e.preventDefault(); + return false; + break; + } + /** + * Is fired when the user hits a key while editing the value. + * + * @event TK.Value#valuetyping + * + * @param {DOMEvent} event - The native DOM event. + * @param {string} value - The new value of the widget. + */ + this.fire_event("valuetyping", e, O.value); +} +function value_input() { + var O = this.options; + if (O.set === false) return; + if (!this.__editing) return; + if (O.editmode == "immediate") + this.userset("value", O.set ? O.set(this._input.value) : this._input.value); +} +function value_done() { + if (!this.__editing) return; + this.__editing = false; + TK.remove_class(this.element, "toolkit-active"); + this._input.blur(); + /** + * Is fired when editing of the value ends. + * + * @event TK.Value#valuedone + * + * @param {string} value - The new value of the widget. + */ + this.fire_event("valuedone", this.options.value); + this.invalid.value = true; + this.trigger_draw(); +} + +function submit_cb(e) { + e.preventDefault(); + return false; +} +/** + * TK.Value is a formatted text field displaying values and providing + * an input field for editing the value. + * + * @class TK.Value + * + * @extends TK.Widget + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {Number} [options.value=0] - The value. + * @property {Function} [options.format=TK.FORMAT("%.2f")] - A formatting + * function used to display the value. + * @property {Integer} [options.size=5] - Size attribute of the INPUT element. + * @property {Integer} [options.maxlength] - Maxlength attribute of the INPUT element. + * @property {Function} [options.set=function (val) { return parseFloat(val || 0); }] - + * A function which is called to parse user input. + * @property {boolean} [options.auto_select=false] - Select the entire text in the entry field if clicked (new in v1.3). + * @property {boolean} [options.readonly=false] - Sets the readonly attribute (new in v1.3). + * @property {string} [options.placeholder=""] - Sets the placeholder attribute (new in v1.3). + * @property {string} [options.type="text"] - Sets the type attribute. Type can be either `text` or `password` (new in v1.3). + * @property {string} [options.editmode="onenter"] - Sets the event to trigger the userset event. Can be one out of `onenter` or `immediate`. + * + */ +TK.Value = TK.class({ + _class: "Value", + Extends: TK.Widget, + _options: Object.assign(Object.create(TK.Widget.prototype._options), { + value: "number|string", + format: "function", + size: "number", + maxlength: "int", + set: "object|function|boolean", + auto_select: "boolean", + readonly: "boolean", + placeholder: "string", + type: "string", + editmode: "string", + }), + options: { + value: 0, + format: TK.FORMAT("%.2f"), + size: 5, + container: false, + // set a callback function if value is editable or + // false to disable editing. A function has to return + // the value treated by the parent widget. + set: function (val) { return parseFloat(val || 0); }, + auto_select: false, + readonly: false, + placeholder: "", + type: "text", + editmode: "onenter", + }, + static_events: { + submit: submit_cb, + click: value_clicked, + }, + initialize: function (options) { + var E; + TK.Widget.prototype.initialize.call(this, options); + /** + * @member {HTMLDivElement} TK.Value#element - The main DIV container. + * Has class toolkit-value. + */ + if (!(E = this.element)) this.element = E = TK.element("div"); + TK.add_class(E, "toolkit-value"); + + this.widgetize(E, true, true, true); + + /** + * @member {HTMLInputElement} TK.Value#_input - The text input. + * Has class toolkit-input. + */ + this._input = TK.element("input", "toolkit-input"); + this._input.type = "text"; + E.appendChild(this._input); + + this._value_typing = value_typing.bind(this); + this._value_done = value_done.bind(this); + this._value_input = value_input.bind(this); + + this._input.addEventListener("keyup", this._value_typing); + this._input.addEventListener("input", this._value_input); + this._input.addEventListener("blur", this._value_done); + this.__editing = false; + }, + + redraw: function () { + var I = this.invalid; + var O = this.options; + var E = this._input; + + TK.Widget.prototype.redraw.call(this); + + if (I.size) { + I.size = 0; + E.setAttribute("size", O.size); + } + + if (I.maxlength) { + I.maxlength = 0; + if (O.maxlength) E.setAttribute("maxlength", O.maxlength); + else E.removeAttribute("maxlength"); + } + + if (I.placeholder) { + I.placeholder = 0; + if (O.placeholder) E.setAttribute("placeholder", O.placeholder); + else E.removeAttribute("placeholder"); + } + + if ((I.value || I.format) && !this.__editing) { + I.format = I.value = false; + E.value = O.format(O.value); + } + + if (I.readonly) { + I.readonly = 0; + if (O.readonly) + E.setAttribute("readonly", "readonly"); + else + E.removeAttribute("readonly"); + } + + if (I.type) { + I.type = 0; + E.setAttribute("type", O.type); + } + }, + + destroy: function () { + this._input.removeEventListener("keyup", this._value_typing); + this._input.removeEventListener("blur", this._value_done); + this._input.removeEventListener("input", this._value_input); + this._input.remove(); + TK.Widget.prototype.destroy.call(this); + }, +}); +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/valuebutton.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/valuebutton.js new file mode 100644 index 0000000000..778d29087c --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/valuebutton.js @@ -0,0 +1,218 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +/** + * The useraction event is emitted when a widget gets modified by user interaction. + * The event is emitted for the option value. + * + * @event TK.ValueButton#useraction + * + * @param {string} name - The name of the option which was changed due to the users action + * @param {mixed} value - The new value of the option + */ + +"use strict"; +(function(w, TK){ +TK.ValueButton = TK.class({ + /** + * This widget combines a {@link TK.Button}, a {@link TK.Scale} and a {@link TK.Value}. + * TK.ValueButton uses {@link TK.DragValue} and {@link TK.ScrollValue} + * for setting its value. + * It inherits all options of {@link TK.DragValue} and {@link TK.Scale}. + * + * @class TK.ValueButton + * + * @extends TK.Button + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {Number} [options.value=0] - The value of the widget. + * @property {Number} [options.value_format=function (val) { return val.toFixed(2); }] - Callback to format the value label. + * @property {Number} [options.value_size=5] - Amount of digits of the value input. + * @property {String} [options.direction="polar"] - Direction for changing the value. + * Can be "polar", "vertical" or "horizontal". See {@link TK.DragValue} for more details. + * @property {Number} [options.blind_angle=20] - If `options.direction` is "polar", + * this is the angle of separation between positive and negative value changes. See {@link TK.DragValue} for more details. + * @property {Number} [options.rotation=45] - Defines the angle of the center of the positive value + * changes. 0 means straight upward. For instance, a value of 45 leads to increasing value when + * moving towards top and right. See {@link TK.DragValue} for more details. + * @property {Number} [options.snap=0.01] - Snap value while dragging. + * @property {Number} [options.basis=300] - Distance to drag between min and max in pixels. + */ + _class: "ValueButton", + Extends: TK.Button, + Implements: [TK.Warning, TK.Ranged], + _options: Object.assign(Object.create(TK.Button.prototype._options), TK.Ranged.prototype._options, { + value: "number", + value_format: "function", + value_size: "number", + drag_direction: "string", + rotation: "number", + blind_angle: "number", + snap: "number", + reset: "number", + }), + options: { + layout: "bottom", + value: 0, + value_format: function (val) { return val.toFixed(2); }, + value_size: 5, + drag_direction: "polar", + rotation: 45, + blind_angle: 20, + snap: 0.01, + basis: 300, + labels: TK.FORMAT("%d"), + }, + static_events: { + set_drag_direction: function(value) { + this.drag.set("direction", value); + }, + set_drag_rotation: function(value) { + this.drag.set("rotation", value); + }, + set_blind_angle: function(value) { + this.drag.set("blind_angle", value); + }, + }, + initialize: function (options) { + TK.Button.prototype.initialize.call(this, options); + + /** + * @member {HTMLDivElement} TK.ValueButton#element - The main DIV container. + * Has class toolkit-valuebutton. + */ + TK.add_class(this.element, "toolkit-valuebutton"); + + /** + * @member {TK.DragValue} TK.ValueButton#drag - The {@link TK.DragValue} module. + */ + this.drag = new TK.DragValue(this, { + node: this.element, + direction: this.options.drag_direction, + rotation: this.options.rotation, + blind_angle: this.options.blind_angle, + }); + /** + * @member {TK.ScrollValue} TK.ValueButton#scroll - The {@link ScrollValue} module. + */ + this.scroll = new TK.ScrollValue(this, { + node: this.element, + }); + + if (this.options.reset === void(0)) + this.options.reset = this.options.value; + this.element.addEventListener("dblclick", function () { + this.userset("value", this.options.reset); + /** + * Is fired when the user doubleclicks the valuebutton in order to to reset to initial value. + * The Argument is the new value. + * + * @event TK.ValueButton#doubleclick + * + * @param {number} value - The value of the widget. + */ + this.fire_event("doubleclick", this.options.value); + }.bind(this)); + }, + destroy: function () { + this.drag.destroy(); + this.scroll.destroy(); + this.scale.destroy(); + TK.Button.prototype.destroy.call(this); + }, + // GETTERS & SETTERS + set: function (key, value) { + switch (key) { + case "value": + if (value > this.options.max || value < this.options.min) + this.warning(this.element); + value = this.snap(value); + break; + } + return TK.Button.prototype.set.call(this, key, value); + } +}); +function value_clicked() { + var self = this.parent; + self.scroll.set("active", false); + self.drag.set("active", false); + /** + * Is fired when the user starts editing the value manually + * + * @event TK.ValueButton#valueedit + * + * @param {number} value - The value of the widget. + */ + self.fire_event("valueedit", self.options.value); +} +function value_done() { + var self = this.parent; + self.scroll.set("active", true); + self.drag.set("active", true); + /** + * Is fired when the user finished editing the value manually + * + * @event TK.ValueButton#valueset + * + * @param {number} value - The value of the widget. + */ + self.fire_event("valueset", self.options.value); +} +/** + * @member {TK.Value} TK.ValueButton#value - The value widget for editing the value manually. + */ +TK.ChildWidget(TK.ValueButton, "value", { + create: TK.Value, + show: true, + map_options: { + value: "value", + value_format: "format", + value_size: "size", + }, + userset_delegate: true, + static_events: { + dblclick: function(e) { + e.stopPropagation(); + }, + valueclicked: value_clicked, + valuedone: value_done, + }, +}); + +/** + * @member {TK.Scale} TK.ValueButton#scale - The {@link TK.Scale} showing the value. + */ +TK.ChildWidget(TK.ValueButton, "scale", { + create: TK.Scale, + show: true, + toggle_class: true, + inherit_options: true, + map_options: { + value: "bar", + }, + static_events: { + "set_layout" : function (v) { + if (v == "horizontal") this.scale.set("layout", "bottom"); + if (v == "vertical") this.scale.set("layout", "left"); + }, + }, +}); + +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/valueknob.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/valueknob.js new file mode 100644 index 0000000000..d6321ded67 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/valueknob.js @@ -0,0 +1,144 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + /** + * The useraction event is emitted when a widget gets modified by user interaction. + * The event is emitted for the option value. + * + * @event TK.ValueKnob#useraction + * + * @param {string} name - The name of the option which was changed due to the users action + * @param {mixed} value - The new value of the option + */ + +"use strict"; +(function(w, TK){ +function value_clicked() { + var self = this.parent; + var knob = self.knob; + knob.scroll.set("active", false); + knob.drag.set("active", false); + /** + * Is fired when the user starts editing the value manually. + * + * @event TK.ValueButton#valueedit + * + * @param {number} value - The value of the widget. + */ + self.fire_event("valueedit", this.options.value); +} +function value_done() { + var self = this.parent; + var knob = self.knob; + knob.scroll.set("active", true); + knob.drag.set("active", true); + /** + * Is fired when the user finished editing the value manually. + * + * @event TK.ValueButton#valueset + * + * @param {number} value - The value of the widget. + */ + self.fire_event("valueset", this.options.value); +} +TK.ValueKnob = TK.class({ + /** + * This widget combines a {@link TK.Knob}, a {@link TK.Label} and a {@link TK.Value} whose + * value is synchronized. It inherits all options from {@link TK.Knob} and {@link TK.Value}. + * + * @class TK.ValueKnob + * + * @extends TK.Widget + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {String} [options.title=false] - Title of the knob. Set to `false` to hide the element from the DOM. + * @property {Function} [options.value_format=TK.FORMAT("%.2f")] - Callback to format the value. + * @property {Number} [options.value_set=function (val) { return parseFloat(val || 0); }] - A function which is called to parse user input in the {@link TK.Value} + * @property {Number} [options.value_size=5] - Amount of digits for the value input. + * @property {Number} [options.show_value=true] - Set to `false` to hide the {@link TK.Value}. + * @property {Number} [options.show_knob=true] - Set to `false` to hide the {@link TK.Knob}. + */ + _class: "ValueKnob", + Extends: TK.Widget, + _options: Object.create(TK.Widget.prototype._options), + options: { }, + initialize: function (options) { + TK.Widget.prototype.initialize.call(this, options); + var E; + /** + * @member {HTMLDivElement} TK.ValueKnob#element - The main DIV container. + * Has class toolkit-valueknob. + */ + if (!(E = this.element)) this.element = E = TK.element("div"); + TK.add_class(E, "toolkit-valueknob"); + + this.widgetize(E, true, true, true); + }, + get_range: function() { + return this.knob.get_range(); + }, + set: function (key, value) { + /* this gets triggered twice, but we need it in order to make the snapping work */ + if (key === "value" && this.knob) + value = this.knob.set("value", value); + + return TK.Widget.prototype.set.call(this, key, value); + }, +}); +/** + * @member {TK.Label} TK.ValueKnob#label - The {@link TK.Label} widget. + */ +TK.ChildWidget(TK.ValueKnob, "label", { + create: TK.Label, + option: "title", + toggle_class: true, + map_options: { + title: "label", + }, +}); +/** + * @member {TK.Knob} TK.ValueKnob#knob - The {@link TK.Knob} widget. + */ +TK.ChildWidget(TK.ValueKnob, "knob", { + create: TK.Knob, + show: true, + inherit_options: true, + toggle_class: true, + userset_delegate: true, +}); +/** + * @member {TK.Value} TK.ValueKnob#value - The {@link TK.Value} widget. + */ +TK.ChildWidget(TK.ValueKnob, "value", { + create: TK.Value, + show: true, + inherit_options: true, + map_options: { + value_format: "format", + value_set: "set", + value_size: "size", + }, + static_events: { + valueclicked: value_clicked, + valuedone: value_done, + }, + toggle_class: true, + userset_delegate: true, +}); +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/widget.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/widget.js new file mode 100644 index 0000000000..ab598d9e79 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/widget.js @@ -0,0 +1,799 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +"use strict"; + +(function(w, TK){ + +function Invalid(options) { + for (var key in options) this[key] = true; +}; +Invalid.prototype = { + validate : function() { + var i = 0, key; + var ret = false; + for (i = 0; i < arguments.length; i++) { + key = arguments[i]; + if (this.hasOwnProperty(key) && this[key]) { + this[key] = false; + ret = true; + } + } + + return ret; + }, + test : function() { + var i = 0, key; + for (i = 0; i < arguments.length; i++) { + key = arguments[i]; + if (this.hasOwnProperty(key) && this[key]) { + return true; + } + } + } +}; +function redraw(fun) { + if (!this._drawn) return; + this.needs_redraw = false; + /** + * Is fired when a redraw is executed. + * + * @event TK.Widget#redraw + */ + this.fire_event("redraw"); + fun.call(this); +} +function resize() { + if (this.is_destructed()) return; + this.resize(); +} +function dblclick (e) { + /** + * Is fired after a double click appeared. Set `dblclick` to 0 to + * disable click event handling. + * + * @event TK.Widget#doubleclick + * + * @param {string} event - The browsers `MouseEvent`. + * + */ + var O = this.options; + var dbc = O.dblclick; + if (!dbc) return; + var d = + new Date(); + if (this.__lastclick + dbc > d) { + e.lastclick = this.__lastclick; + this.fire_event("doubleclick", e); + this.__lastclick = 0; + } else { + this.__lastclick = d; + } +} + +TK.Widget = TK.class({ + /** + * TK.Widget is the base class for all widgets drawing DOM elements. It + * provides basic functionality like delegating events, setting options and + * firing some events. + * + * @class TK.Widget + * + * @extends TK.Base + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {String} [options.class=""] - A class to add to the class attribute of the main element. + * @property {HTMLElement} [options.container] - A container the main element shall be added to. + * @property {String} [options.id=""] - A string to be set as id attribute on the main element. + * @property {Object} [options.styles=""] - An object containing CSS declarations to be added directly to the main element. + * @property {Boolean} [options.disabled=false] - Toggles the class toolkit-disabled. + * @property {HTMLElement} [options.element] - An element to be used as the main element. + * @property {Boolean} [options.active] - Toggles the class toolkit-inactive. + * @property {Boolean} [options.needs_resize=true] - Set to true if the resize function shall be called before the next redraw. + * @property {Boolean} [options.dblclick=400] - Set a time in milliseconds for triggering double click event. If 0, no double click events are fired. + */ + /** + * The set event is emitted when an option was set using the {@link TK.Widget#set} + * method. The arguments are the option name and its new value. + * + * Note that this happens both for user interaction and programmatical option changes. + * + * @event TK.Widget#set + */ + /** + * The redraw event is emitted when a widget is redrawn. This can be used + * to do additional DOM modifications to a Widget. + * + * @event TK.Widget#redraw + */ + /** + * The resize event is emitted whenever a widget is being resized. This event can + * be used to e.g. measure its new size. Note that some widgets do internal adjustments after + * the resize event. If that is relevant, the {@link TK.Widget#resized} event can + * be used, instead. + * + * @event TK.Widget#resize + */ + /** + * The resized event is emitted after each rendering frame, which was triggered by + * a resize event. + * + * @event TK.Widget#resized + */ + /** + * The hide event is emitted when a widget is hidden and is not rendered anymore. + * This happens both with browser visibility changes and also internally when using layout widgets + * such as {@link TK.Pager}. + * + * @event TK.Widget#hide + */ + /** + * The show event is emitted when a widget is shown and is being rendered. This is the + * counterpart to {@link TK.Widget#hide}. + * + * @event TK.Widget#show + */ + Extends : TK.Base, + _class: "Widget", + _options: { + // A CSS class to add to the main element + class: "string", + // A DOM element as container to inject the element + // into + container: "object", + // a id to set on the element. If omitted a random + // string is generated. + id: "string", + // If an element was stylized, styles can be applied + styles: "object", + disabled: "boolean", + element: "object", + active: "boolean", + needs_resize: "boolean", + dblclick: "number", + }, + options: { + // these options are of less use and only here to show what we need + disabled: false, // Widgets can be disabled by setting this to true + needs_resize: true, + dblclick: 0, + }, + static_events: { + set_container: function(value) { + if (value && this.element) { + value.appendChild(this.element); + } else if (!value && this.element.parentElement) { + this.element.parentElement.removeChild(this.element); + } + }, + set_dblclick: function (val) { + if (!this.__delegated) return; + if (!!val) + this.__delegated.addEventListener("click", this.__dblclick_cb); + else + this.__delegated.removeEventListener("click", this.__dblclick_cb); + }, + initialized: function () { + var v = this.options.dblclick; + if (v > 0) + this.set("dblclick", v); + }, + }, + initialize: function (options) { + // Main actions every widget needs to take + if (!options) options = {}; + /** @property {HTMLElement} TK.Widget#element - The main element. */ + if (options.element) + this.element = options.element; + TK.Base.prototype.initialize.call(this, options); + this.__classified = null; + this.__stylized = null; + this.__delegated = null; + this.__widgetized = null; + this.invalid = new Invalid(this.options); + if (!this.value_time) this.value_time = null; + this.needs_redraw = false; + this._redraw = redraw.bind(this, this.redraw); + this.__resize = resize.bind(this); + this._schedule_resize = this.schedule_resize.bind(this); + this._drawn = false; + this.parent = null; + this.children = null; + this.draw_queue = null; + this.__lastclick = 0; + this.__dblclick_cb = dblclick.bind(this); + }, + + is_destructed: function() { + return this.options === null; + }, + + invalidate_all: function() { + for (var key in this.options) { + if (!this._options[key]) { + if (key.charCodeAt(0) !== 95) + TK.warn("%O %s: unknown option %s", this, this._class, key); + } else this.invalid[key] = true; + } + }, + + assert_none_invalid: function() { + var warn = []; + for (var key in this.invalid) { + if (this.invalid[key] === true) { + warn.push(key); + } + } + + if (warn.length) { + TK.warn("found", warn.length, "invalid in", this, ":", warn); + } + }, + + trigger_resize: function() { + if (!this.options.needs_resize) { + if (this.is_destructed()) { + // This object was destroyed but trigger resize was still scheduled for the next frame. + // FIXME: fix this whole problem properly + return; + } + + this.set("needs_resize", true); + + var C = this.children; + + if (!C) return; + + for (var i = 0; i < C.length; i++) { + C[i].trigger_resize(); + } + } + }, + + trigger_resize_children: function() { + var C = this.children; + + if (!C) return; + + for (var i = 0; i < C.length; i++) { + C[i].trigger_resize(); + } + }, + + schedule_resize: function() { + TK.S.add(this.__resize, 0); + }, + + resize: function() { + /** + * Is fired when a resize is requested. + * + * @event TK.Widget#resize + */ + this.fire_event("resize"); + + if (this._options.resized) + this.set("resized", true); + + /** + * Is fired after the resize was executed and the DOM is updated. + * + * @event TK.Widget#resized + */ + if (this.has_event_listeners("resized")) { + TK.S.after_frame(this.fire_event.bind(this, "resized")); + } + }, + + trigger_draw: function() { + if (!this.needs_redraw) { + this.needs_redraw = true; + if (this._drawn) TK.S.add(this._redraw, 1); + } + }, + + trigger_draw_next : function() { + if (!this.needs_redraw) { + this.needs_redraw = true; + if (this._drawn) TK.S.add_next(this._redraw, 1); + } + }, + + initialized: function () { + // Main actions every widget needs to take + /** + * Is fired when a widget is initialized. + * + * @event TK.Widget#initialized + */ + this.fire_event("initialized"); + this.trigger_draw(); + }, + draw_once: function(fun) { + var q = this.draw_queue; + + if (q === null) { + this.draw_queue = [ fun ]; + } else { + for (var i = 0; i < q.length; i++) if (q[i] === fun) return; + q[i] = fun; + } + this.trigger_draw(); + }, + redraw: function () { + var I = this.invalid; + var O = this.options; + var E = this.element; + + if (E) { + if (I.id) { + I.id = false; + if (O.id) E.setAttribute("id", O.id); + } + } + + E = this.__stylized; + + if (E) { + if (I.active) { + I.active = false; + TK.toggle_class(E, "toolkit-inactive", !O.active); + } + + if (I.disabled) { + I.disabled = false; + TK.toggle_class(E, "toolkit-disabled", O.disabled); + } + + if (I.styles) { + I.styles = false; + if (O.styles) TK.set_styles(E, O.styles); + } + } + + if (I.needs_resize) { + I.needs_resize = false; + + if (O.needs_resize) { + O.needs_resize = false; + + TK.S.after_frame(this._schedule_resize); + } + } + + var q = this.draw_queue; + + this.draw_queue = null; + + if (q) for (var i = 0; i < q.length; i++) { + q[i].call(this, O); + } + }, + destroy: function () { + /** + * Is fired when a widget is destroyed. + * + * @event TK.Widget#destroy + */ + if (this.is_destructed()) { + TK.warn("destroy called twice on ", this); + return; + } + this.fire_event("destroy"); + + this.disable_draw(); + if (this.parent) this.parent.remove_child(this); + + TK.Base.prototype.destroy.call(this); + + this._redraw = null; + this.__resize = null; + this._schedule_resize = null; + this.children = null; + this.options = null; + this.parent = null; + + if (this.element) { + this.element.remove(); + this.element = null; + } + }, + delegate: function (element) { + this.delegate_events(element); + this.__delegated = element; + /** + * Is fired when a widget gets delegated. + * + * @event TK.Widget#initialized + * + * @param {HTMLElement} element - The element all native DOM events are delegated to. + */ + this.fire_event("delegated", element); + return element; + }, + add_class: function (cls) { + TK.add_class(this.__classified, cls); + }, + remove_class: function (cls) { + TK.remove_class(this.__classified, cls); + }, + has_class: function (cls) { + return TK.has_class(this.__classified, cls); + }, + classify: function (element) { + // Takes a DOM element and adds its CSS functionality to the + // widget instance + this.__classified = element; + if (this.options.class && element) + TK.add_class(element, this.options.class); + /** + * Is fired when a widget is classified. + * + * @event TK.Widget#classified + * + * @param {HTMLElement} element - The element which receives all further class changes. + */ + this.fire_event("classified", element); + return element; + }, + set_style: function (name, value) { + TK.set_style(this.__stylized, name, value); + }, + /** + * Sets a CSS style property in this widgets DOM element. + * + * @method TK.Widget#set_style + */ + set_styles: function (styles) { + TK.set_styles(this.__stylized, styles); + }, + /** + * Returns the computed style of this widgets DOM element. + * + * @method TK.Widget#get_style + */ + get_style: function (name) { + return TK.get_style(this.__stylized, name); + }, + stylize: function (element) { + // Marks a DOM element as receiver for the "styles" options + this.__stylized = element; + if (this.options.styles) { + TK.set_styles(element, this.options.styles); + } + /** + * Is fired when a widget is stylized. + * + * @event TK.Widget#stylized + * + * @param {HTMLElement} element - The element which receives all further style changes. + */ + this.fire_event("stylized", element); + return element; + }, + widgetize: function (element, delegate, classify, stylize) { + /** + * Set the DOM elements of this widgets. This method is usually only used internally. + * Basically it means to add the id from options and set a basic CSS class. + * If delegate is true, basic events will be delegated from the element to the widget instance + * if classify is true, CSS functions will be bound to the widget instance. + * + * @method TK.Widget#widgetize + * @emits TK.Widget#widgetize + */ + var O = this.options; + + // classify? + TK.add_class(element, "toolkit-widget"); + if (typeof O.id !== "string") { + O.id = element.getAttribute("id"); + if (!O.id) { + O.id = TK.unique_id() + element.setAttribute("id", O.id); + } + } else element.setAttribute("id", O.id); + + if (O.class) { + var c = O.class.split(" "); + for (var i = 0; i < c.length; i++) + TK.add_class(element, c[i]); + } + if (O.container) + O.container.appendChild(element); + if (delegate) + this.delegate(element); + if (classify) + this.classify(element); + if (stylize) + this.stylize(element); + this.__widgetized = element; + /** + * Is fired when a widget is widgetized. + * + * @event TK.Widget#widgetize + * + * @param {HTMLElement} element - The element which got widgetized. + */ + this.fire_event("widgetized", element); + return element; + }, + + // GETTER & SETTER + /** + * Sets an option. + * + * @method TK.Widget#set + * + * @param {string} key - The option name. + * @param value - The option value. + */ + set: function (key, value) { + /* These options are special and need to be handled immediately, in order + * to preserve correct ordering */ + if (key === "class" && this.__classified) { + if (this.options.class) TK.remove_class(this.__classified, this.options.class); + if (value) TK.add_class(this.__classified, value); + } + if (this._options[key]) { + this.invalid[key] = true; + if (this.value_time && this.value_time[key]) + this.value_time[key] = Date.now(); + this.trigger_draw(); + } else if (key.charCodeAt(0) !== 95) { + TK.warn("%O: %s.set(%s, %O): unknown option.", this, this._class, key, value); + } + TK.Base.prototype.set.call(this, key, value); + return value; + }, + track_option: function(key) { + if (!this.value_time) this.value_time = {}; + this.value_time[key] = Date.now(); + }, + /** + * Schedules this widget for drawing. + * + * @method TK.Widget#enable_draw + * + * @emits TK.Widget#show + */ + enable_draw: function () { + if (this._drawn) return; + this._drawn = true; + if (this.needs_redraw) { + TK.S.add(this._redraw, 1); + } + /** + * Is fired when a widget gets enabled for drawing. + * + * @event TK.Widget#show + */ + this.fire_event("show"); + this.fire_event("visibility", true); + var C = this.children; + if (C) for (var i = 0; i < C.length; i++) C[i].enable_draw(); + }, + /** + * Stop drawing this widget. + * + * @method TK.Widget#enable_draw + * + * @emits TK.Widget#hide + */ + disable_draw: function () { + if (!this._drawn) return; + this._drawn = false; + if (this.needs_redraw) { + TK.S.remove(this._redraw, 1); + TK.S.remove_next(this._redraw, 1); + } + /** + * Is fired when a widget is hidden and not rendered anymore. + * + * @event TK.Widget#hide + */ + /** + * Is fired when the visibility state changes. The first argument + * is the visibility state, which is either true + * or false. + * + * @event TK.Widget#visibility + */ + this.fire_event("hide"); + this.fire_event("visibility", false); + var C = this.children; + if (C) for (var i = 0; i < C.length; i++) C[i].disable_draw(); + }, + /** + * Make the widget visible. This does not modify the DOM, instead it will only schedule + * the widget for rendering. + * + * @method TK.Widget#show + */ + show: function () { + this.enable_draw(); + }, + /** + * This is an alias for hide, which may be overloaded. + * See {@link TK.Container} for an example. + * + * @method TK.Widget#force_show + */ + force_show: function() { + this.enable_draw(); + }, + /** + * Make the widget hidden. This does not modify the DOM, instead it will stop rendering + * this widget. Options changed after calling hide will only be rendered (i.e. applied + * to the DOM) when the widget is made visible again using {@link TK.Widget#show}. + * + * @method TK.Widget#hide + */ + hide: function () { + this.disable_draw(); + }, + /** + * This is an alias for hide, which may be overloaded. + * See {@link TK.Container} for an example. + * + * @method TK.Widget#force_hide + */ + force_hide: function () { + this.disable_draw(); + }, + show_nodraw: function() { }, + hide_nodraw: function() { }, + /** + * Returns the current hidden status. + * + * @method TK.Widget#hidden + */ + hidden: function() { + return !this._drawn; + }, + is_drawn: function() { + return this._drawn; + }, + /** + * TK.Toggle the hidden status. This is equivalent to calling hide() or show(), depending on + * the current hidden status of this widget. + * + * @method TK.Widget#toggle_hidden + */ + toggle_hidden: function() { + if (this.hidden()) this.show(); + else this.hide(); + }, + set_parent: function(parent) { + if (this.parent) { + this.parent.remove_child(this); + } + this.parent = parent; + }, + /** + * Registers a widget as a child widget. This method is used to build up the widget tree. It does not modify the DOM tree. + * + * @method TK.Widget#add_child + * + * @param {TK.Widget} child - The child to add. + * + * @see TK.Container#append_child + */ + add_child: function(child) { + var C = this.children; + if (!C) this.children = C = []; + child.set_parent(this); + C.push(child); + if (!this.hidden()) { + child.enable_draw(); + } else { + child.disable_draw(); + } + child.trigger_resize(); + }, + /** + * Removes a child widget. Note that this method only modifies + * the widget tree and does not change the DOM. + * + * @method TK.Widget#remove_child + * + * @param {TK.Widget} child - The child to remove. + */ + remove_child : function(child) { + child.disable_draw(); + child.parent = null; + var C = this.children; + if (C === null) return; + var i = C.indexOf(child); + if (i !== -1) { + C.splice(i, 1); + } + if (!C.length) this.children = null; + }, + /** + * Removes an array of children. + * + * @method TK.Widget#remove_children + * + * @param {Array.} a - An array of Widgets. + */ + remove_children : function(a) { + a.map(this.remove_child, this); + }, + /** + * Registers an array of widgets as children. + * + * @method TK.Widget#add_children + * + * @param {Array.} a - An array of Widgets. + */ + add_children : function (a) { + a.map(this.add_child, this); + }, + + /** + * Returns an array of all visible children. + * + * @method TK.Widget#visible_children + */ + visible_children: function(a) { + if (!a) a = []; + var C = this.children; + if (C) for (var i = 0; i < C.length; i++) { + a.push(C[i]); + C[i].visible_children(a); + } + return a; + }, + + /** + * Returns an array of all children. + * + * @method TK.Widget#all_children + */ + all_children: function(a) { + if (!a) a = []; + var C = this.children; + if (C) for (var i = 0; i < C.length; i++) { + a.push(C[i]); + C[i].all_children(a); + } + return a; + }, +}); + +TK.Module = TK.class({ + Extends: TK.Base, + initialize: function(widget, options) { + this.parent = widget; + TK.Base.prototype.initialize.call(this, options); + }, + destroy: function() { + this.parent = null; + TK.Base.prototype.destroy.call(this); + }, +}); +})(this, this.TK); + +/** + * Generic DOM events. Please refer to + * + * W3Schools + * for further details. + * + * @event TK.Widget##GenericDOMEvents + */ diff --git a/share/web_surfaces/builtin/mixer/toolkit/widgets/window.js b/share/web_surfaces/builtin/mixer/toolkit/widgets/window.js new file mode 100644 index 0000000000..65895e7662 --- /dev/null +++ b/share/web_surfaces/builtin/mixer/toolkit/widgets/window.js @@ -0,0 +1,819 @@ +/* + * This file is part of Toolkit. + * + * Toolkit is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Toolkit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +"use strict"; +(function(w, TK){ +function header_action() { + var that = this.parent; + switch (that.options.header_action) { + case "shrink": that.toggle_shrink(); break; + case "maximize": that.toggle_maximize(); break; + case "maximizehorizontal": that.toggle_maximize_horizontal(); break; + case "maximizevertical": that.toggle_maximize_vertical(); break; + case "minimize": that.toggle_minimize(); break; + case "close": that.destroy(); break; + } + /** + * The user double-clicked on the header. + * @event TK.Window.headeraction + * @param {string} action - The function which was executed, e.g. shrink, maximize or close. + */ + that.fire_event("headeraction", that.options.header_action); +} +function mout(e) { + if(this.options.auto_active && !this.dragging && !this.resizing) + TK.remove_class(this.element, "toolkit-active"); +} +function mover(e) { + if(this.options.auto_active) + TK.add_class(this.element, "toolkit-active"); +} +function max_height() { + // returns the max height of the window + return (this.options.max_height < 0 ? Number.MAX_SAFE_INTEGER : this.options.max_height); +} +function max_width() { + // returns the max width of the window + return (this.options.max_width < 0 ? Number.MAX_SAFE_INTEGER : this.options.max_width); +} +function close(e) { + /** + * The user clicked the close button. + * @event TK.Window.closeclicked + */ + this.fire_event("closeclicked"); + if (this.options.auto_close) + this.destroy(); +} +function maximize(e) { + if (this.options.auto_maximize) this.toggle_maximize(); + /** + * The user clicked the maximize button. + * @event TK.Window.maximizeclicked + * @param {Object} maximize - The maximize option. + */ + this.fire_event("maximizeclicked", this.options.maximize); +} +function maximizevertical(e) { + if (this.options.auto_maximize) this.toggle_maximize_vertical(); + /** + * The user clicked the maximize-vertical button. + * @event TK.Window.maximizeverticalclicked + * @param {Object} maximize - The maximize option. + */ + this.fire_event("maximizeverticalclicked", this.options.maximize.y); +} +function maximizehorizontal(e) { + if (this.options.auto_maximize) this.toggle_maximize_horizontal(); + /** + * The user clicked the maximize-horizontal button. + * @event TK.Window.maximizehorizontalclicked + * @param {Object} maximize - The maximize option. + */ + this.fire_event("maximizehorizontalclicked", this.options.maximize.x); +} +function minimize(e) { + if (this.options.auto_minimize) this.toggle_minimize(); + /** + * The user clicked the minimize button. + * @event TK.Window.minimizeclicked + * @param {Object} minimize - The minimize option. + */ + this.fire_event("minimizeclicked", this.options.minimize); +} +function shrink(e) { + if (this.options.auto_shrink) this.toggle_shrink(); + /** + * The user clicked the shrink button. + * @event TK.Window.shrinkclicked + * @param {Object} shrink - The shrink option. + */ + this.fire_event("shrinkclicked", this.options.shrink); +} +function start_resize(el, ev) { + this.global_cursor("se-resize"); + this.resizing = true; + TK.add_class(this.element, "toolkit-resizing"); + /** + * The user starts resizing the window. + * @event TK.Window.startresize + * @param {DOMEvent} event - The DOM event. + */ + this.fire_event("startresize", ev); +} +function stop_resize(el, ev) { + this.remove_cursor("se-resize"); + this.resizing = false; + TK.remove_class(this.element, "toolkit-resizing"); + this.trigger_resize_children(); + calculate_dimensions.call(this); + /** + * The user stops resizing the window. + * @event TK.Window.stopresize + * @param {DOMEvent} event - The DOM event. + */ + this.fire_event("stopresize", ev); +} +function resizing(el, ev) { + if (this.options.resizing === "continuous") { + this.trigger_resize_children(); + calculate_dimensions.call(this); + } + /** + * The user resizes the window. + * @event TK.Window.resizing + * @param {DOMEvent} event - The DOM event. + */ + this.fire_event("resizing", ev); +} +function calculate_dimensions() { + var x = TK.outer_width(this.element, true); + var y = TK.outer_height(this.element, true); + this.dimensions.width = this.options.width = x; + this.dimensions.height = this.options.height = y; + this.dimensions.x2 = x + this.dimensions.x1; + this.dimensions.y2 = y + this.dimensions.y1; +} +function calculate_position() { + var posx = TK.position_left(this.element); + var posy = TK.position_top(this.element); + var pos1 = this.translate_anchor(this.options.anchor, posx, posy, + this.options.width, this.options.height); + this.dimensions.x = this.options.x = pos1.x; + this.dimensions.y = this.options.y = pos1.y; + this.dimensions.x1 = posx; + this.dimensions.y1 = posy; + this.dimensions.x2 = posx + this.dimensions.width; + this.dimensions.y2 = posy + this.dimensions.height; +} +function horiz_max() { + // returns true if maximized horizontally + return this.options.maximize.x; +} +function vert_max() { + // returns if maximized vertically + return this.options.maximize.y; +} +function start_drag(ev, el) { + this.global_cursor("move"); + TK.add_class(this.element, "toolkit-dragging"); + // if window is maximized, we have to replace the window according + // to the position of the mouse + var x = y = 0; + if (vert_max.call(this)) { + var y = (!this.options.fixed ? window.scrollY : 0); + } + if (horiz_max.call(this)) { + var x = ev.clientX - (ev.clientX / TK.width()) + * this.options.width; + x += (!this.options.fixed ? window.scrollX : 0); + } + var pos = this.translate_anchor( + this.options.anchor, x, y, this.options.width, this.options.height); + + if (horiz_max.call(this)) this.options.x = pos.x; + if (vert_max.call(this)) this.options.y = pos.y; + + this.Drag._xpos += x; + this.Drag._ypos += y; + + /** + * The user starts dragging the window. + * @event TK.Window.startdrag + * @param {DOMEvent} event - The DOM event. + */ + this.fire_event("startdrag", ev); +} +function stop_drag(ev, el) { + this.dragging = false; + calculate_position.call(this); + this.remove_cursor("move"); + /** + * The user stops dragging the window. + * @event TK.Window.stopdrag + * @param {DOMEvent} event - The DOM event. + */ + this.fire_event("stopdrag", ev); +} +function dragging(ev, el) { + if (!this.dragging) { + this.dragging = true; + // un-maximize + if (horiz_max.call(this)) { + this.set("maximize", {x: false}); + } + if (vert_max.call(this)) { + this.set("maximize", {y: false}); + } + } + calculate_position.call(this); + /** + * The user is dragging the window. + * @event TK.Window.dragging + * @param {DOMEvent} event - The DOM event. + */ + this.fire_event("dragging", ev); +} +function init_position(pos) { + var O = this.options; + if (pos) { + var x0 = O.fixed ? 0 : window.scrollX; + var y0 = O.fixed ? 0 : window.scrollY; + var pos1 = this.translate_anchor( + O.open, x0, y0, + window.innerWidth - O.width, + window.innerHeight - O.height); + var pos2 = this.translate_anchor( O.anchor, pos1.x, pos1.y, O.width, O.height); + O.x = pos2.x; + O.y = pos2.y; + } + set_dimensions.call(this); + set_position.call(this); +} +function set_position() { + var O = this.options; + var D = this.dimensions; + var width = TK.inner_width(this.element); + var height = TK.inner_height(this.element); + var pos = this.translate_anchor(O.anchor, O.x, O.y, -width, -height); + if (horiz_max.call(this)) { + this.element.style.left = (O.fixed ? 0 : window.scrollX) + "px"; + } else { + this.element.style.left = pos.x + "px"; + } + if (vert_max.call(this)) { + this.element.style.top = (O.fixed ? 0 : window.scrollY) + "px"; + } else { + this.element.style.top = pos.y + "px"; + } + D.x = O.x; + D.y = O.y; + D.x1 = pos.x; + D.y1 = pos.y; + D.x2 = pos.x + D.width; + D.y2 = pos.y + D.height; + /** + * The position of the window changed. + * @event TK.Window.positionchanged + * @param {Object} event - The {@link TK.Window#dimensions} dimensions object. + */ + this.fire_event("positionchanged", D); +} +function set_dimensions() { + var O = this.options; + var D = this.dimensions; + if (O.width >= 0) { + O.width = Math.min(max_width.call(this), Math.max(O.width, O.min_width)); + if (horiz_max.call(this)) { + TK.outer_width(this.element, true, TK.width()); + D.width = TK.width(); + } else { + TK.outer_width(this.element, true, O.width); + D.width = O.width; + } + } else { + D.width = TK.outer_width(this.element); + } + if (O.height >= 0) { + O.height = Math.min(max_height.call(this), Math.max(O.height, O.min_height)); + if (vert_max.call(this)) { + TK.outer_height(this.element, true, TK.height()); + D.height = TK.height(); + } else { + TK.outer_height(this.element, true, O.height); + D.height = O.height; + } + } else { + D.height = TK.outer_height(this.element, true); + } + D.x2 = D.x1 + D.width; + D.y2 = D.y1 + D.height; + /** + * The dimensions of the window changed. + * @event TK.Window.dimensionschanged + * @param {Object} event - The {@link TK.Window#dimensions} dimensions object. + */ + this.fire_event("dimensionschanged", this.dimensions); +} +function build_header() { + build_from_const.call(this, "header"); + if (!this.Drag) { + this.Drag = new TK.Drag({ + node : this.element, + handle : this.header.element, + onStartdrag : start_drag.bind(this), + onStopdrag : stop_drag.bind(this), + onDragging : dragging.bind(this), + min : {x: 0 - this.options.width + 20, y: 0}, + max : {x: TK.width() - 20, y: TK.height() - 20}, + }); + //this.header.add_event("dblclick", header_action.bind(this)); + } + /** + * The header changed. + * @event TK.Window.headerchanged + */ + this.fire_event("headerchanged"); +} +function build_footer() { + build_from_const.call(this, "footer"); + /** + * The footer changed. + * @event TK.Window.footerchanged + */ + this.fire_event("footerchanged"); +} +function build_from_const(element) { + var E = this[element].element; + var L = this.options[element]; + var O = this.options; + var targ; + while (E.firstChild) E.firstChild.remove(); + if (!L) return; + for (var i = 0; i < L.length; i++) { + if (L[i] !== "spacer") { + this.set("show_" + L[i], true); + E.appendChild(this[L[i]].element); + if (L[i] == "size" && !this.Resize && this.size) { + this.Resize = new TK.Resize({ + node : this.element, + handle : this.size.element, + min : {x: O.min_width, y: O.min_height}, + max : {x: max_width.call(this), y: max_height.call(this)}, + onResizestart : start_resize.bind(this), + onResizestop : stop_resize.bind(this), + onResizing : resizing.bind(this), + active : O.resizable + }); + } + } else { + E.appendChild(TK.element("div", "toolkit-spacer")); + } + } +} + +function status_timeout () { + var O = this.options; + if (this.__status_to !== false) + window.clearTimeout(this.__status_to); + if (!O.hide_status) return; + if (O.status) + this.__status_to = window.setTimeout(function () { + this.set("status", ""); + this.__status_to = false; + }.bind(this), O.hide_status); +} + +TK.Window = TK.class({ + /** + * This widget is a flexible overlay window. + * + * @class TK.Window + * + * @extends TK.Container + * @implments TK.Anchor TK.GlobalCursor + * + * @param {Object} [options={ }] - An object containing initial options. + * + * @property {Number} [options.width=500] - Initial width, can be a CSS length or an integer (pixels). + * @property {Number} [options.height=200] - Initial height, can be a CSS length or an integer (pixels). + * @property {Number} [options.x=0] - X position of the window. + * @property {Number} [options.y=0] - Y position of the window. + * @property {Number} [options.min_width=64] - Minimum width of the window. + * @property {Number} [options.max_width=-1] - Maximum width of the window, -1 ~ infinite. + * @property {Number} [options.min_height=64] - Minimum height of the window. + * @property {Number} [options.max_height=-1] - Maximum height of the window, -1 ~ infinite. + * @property {String} [options.anchor="top-left"] - Anchor of the window, can be one out of + * `top-left`, `top`, `top-right`, `left`, `center`, `right`, `bottom-left`, `bottom`, `bottom-right` + * @property {Boolean} [options.modal=false] - If modal window blocks all other elements + * @property {String} [options.dock=false] - Docking of the window, can be one out of + * `top-left`, `top`, `top-right`, `left`, `center`, `right`, `bottom-left`, `bottom`, `bottom-right` + * @property {Object|Boolean} [options.maximize=false] - Boolean or object with members x and y as boolean to determine the maximized state. + * @property {Boolean} [options.minimize=false] - Minimize window (does only make sense with a + * window manager application to keep track of it) + * @property {Boolean} [options.shrink=false] - Shrink rolls the window up into the title bar. + * @property {String|HTMLElement|TK.Container} [options.content=""] - The content of the window. + * Can be either a string, a HTMLElement or a {@link TK.Container} to be appended to the content area. + * @property {String} [options.open="center"] - initial position of the window, can be one out of + * `top-left`, `top`, `top-right`, `left`, `center`, `right`, `bottom-left`, `bottom`, `bottom-right` + * @property {Integer} [options.z_index=10000] - Z index for piling windows. does make more sense + * when used together with a window manager + * @property {String|Array} [options.header=["title", "maximize", "close"]] - Single element or array of + * `title`, `icon`, `close`, `minimize`, `shrink`, `maximize`, `maximizevertical`, `maximizehorizontal`, `status`, `resize`, `spacer`. + * @property {String|Array} [options.footer=false] - Single element or array of + * `title`, `icon`, `close`, `minimize`, `shrink`, `maximize`, `maximizevertical`, `maximizehorizontal`, `status`, `resize`, `spacer`. + * @property {String} [options.title=false] - Window title. + * @property {String} [options.status=false] Window status. + * @property {String} [options.icon=false] URL to window icon. + * @property {Boolean} [options.fixed=true] - Whether the window sticks to the viewport rather than the document + * @property {Boolean} [options.auto_active=false] - Auto-toggle the active-class when mouseovered + * @property {Boolean} [options.auto_close=true] - Set whether close destroys the window or not + * @property {Boolean} [options.auto_maximize=true] - Set whether maximize toggles the window or not + * @property {Boolean} [options.auto_minimize=true] - Set whether minimize toggles the window or not + * @property {Boolean} [options.auto_shrink=true] - Set whether shrink toggles the window or not + * @property {Boolean} [options.draggable=true] - Set whether the window is draggable + * @property {Boolean} [options.resizable=true] - Set whether the window is resizable + * @property {String} [options.resizing="continuous"] - Resizing policy, `continuous` or `stop`. + * The first one resizes all children continuously while resizing. + * @property {String} [options.header_action="maximize"] - Action for double clicking the window header, one out of + * `close`, `minimize`, `shrink`, `maximize`, `maximizevertical`, `maximizehorizontal` + * @property {Boolean} [options.active=true] - Active state of the window. + * @property {Integer} [options.hide_status=0] - If set to !0 status message hides after [n] milliseconds. + */ + + /** + * @member {TK.Drag} TK.Window#Drag - The {TK.Drag} module. + */ + /** + * @member {TK.Resize} TK.Window#Resize - The {TK.Resize} module. + */ + _class: "Window", + Extends: TK.Container, + Implements: [TK.Anchor, TK.GlobalCursor], + _options: Object.assign(Object.create(TK.Container.prototype._options), { + width: "number", + height: "number", + x: "number", + y: "number", + min_width: "number", + max_width: "number", + min_height: "number", + max_height: "number", + anchor: "string", + modal: "boolean", + dock: "boolean", + maximize: "boolean", + minimize: "boolean", + shrink: "boolean", + open: "int", + z_index: "int", + header: "array", + footer: "array", + title: "string", + status: "string", + icon: "string", + fixed: "boolean", + auto_active: "boolean", + auto_close: "boolean", + auto_maximize: "boolean", + auto_minimize: "boolean", + auto_shrink: "boolean", + draggable: "boolean", + resizable: "boolean", + resizing: "int", + header_action: "int", + active: "boolean", + hide_status: "int", + }), + options: { + width: 500, + height: 200, + x: 0, + y: 0, + min_width: 64, + max_width: -1, + min_height: 64, + max_height: -1, + anchor: "top-left", + modal: false, + dock: false, + maximize: false, + minimize: false, + shrink: false, + content: "", + open: "center", + z_index: 10000, + header: ["title", "maximize", "close"], + footer: false, + title: false, + status: false, + icon: false, + fixed: true, + auto_active: false, + auto_close: true, + auto_maximize: true, + auto_minimize: true, + auto_shrink: true, + draggable: true, + resizable: true, + resizing: "continuous", + header_action: "maximize", + active: true, + hide_status: 0, + }, + static_events: { + mouseenter: mover, + mouseleave: mout, + }, + initialize: function (options) { + this.dimensions = {anchor: "top-left", x: 0, x1: 0, x2: 0, y: 0, y1: 0, y2: 0, width: 0, height: 0}; + TK.Container.prototype.initialize.call(this, options); + var O = this.options; + TK.add_class(this.element, "toolkit-window"); + this.__status_to = false; + init_position.call(this, this.options.open); + this.set("maximize", this.options.maximize); + this.set("minimize", this.options.minimize); + }, + + /** + * Appends a new child to the window content area. + * @method TK.Window#append_child + * @param {TK.Widget} child - The child widget to add to the windows content area. + */ + append_child : function(child) { + child.set("container", this.content.element); + this.add_child(child); + }, + + /** + * Toggles the overall maximize state of the window. + * @method TK.Window#toggle_maximize + * @param {Boolean} maximize - State of maximization. If window is already + * maximized in one or both directions it is un-maximized, otherwise maximized. + */ + toggle_maximize: function () { + if (!vert_max.call(this) || !horiz_max.call(this)) + this.set("maximize", {x: true, y: true}); + else + this.set("maximize", {x: false, y: false}); + }, + /** + * Toggles the vertical maximize state of the window. + * @method TK.Window#toggle_maximize_vertical + * @param {Boolean} maximize - The new vertical maximization. + */ + toggle_maximize_vertical: function () { + this.set("maximize", {y: !this.options.maximize.y}); + }, + /** + * Toggles the horizontal maximize state of the window. + * @method TK.Window#toggle_maximize_horizontal + * @param {Boolean} maximize - The new horizontal maximization. + */ + toggle_maximize_horizontal: function () { + this.set("maximize", {x: !this.options.maximize.x}); + }, + /** + * Toggles the minimize state of the window. + * @method TK.Window#toggle_minimize + * @param {Boolean} minimize - The new minimization. + */ + toggle_minimize: function () { + this.set("minimize", !this.options.minimize); + }, + /** + * Toggles the shrink state of the window. + * @method TK.Window#toggle_shrink + * @param {Boolean} shrink - The new shrink state. + */ + toggle_shrink: function () { + this.set("shrink", !this.options.shrink); + }, + + resize: function () { + this.Drag.set("min", {x: 0 - this.options.width + 20, y: 0}); + this.Drag.set("max", {x: TK.width() - 20, y: TK.height() - 20}); + TK.Container.prototype.resize.call(this); + }, + + redraw: function () { + var I = this.invalid; + var O = this.options; + var E = this.element; + + var setP = false; + var setD = false; + + if (I.maximize) { + I.maximize = false; + if (O.shrink) { + O.shrink = false; + I.shrink = true; + } + TK.toggle_class(this.element, "toolkit-maximized-horizontal", O.maximize.x); + TK.toggle_class(this.element, "toolkit-maximized-vertical", O.maximize.y); + setD = true; + } + if (I.anchor) { + I.anchor = false; + this.dimensions.anchor = O.anchor; + setP = setD = true; + } + if (I.width || I.height) { + I.width = I.height = false; + setD = true; + } + if (I.x || I.y) { + I.x = I.y = false; + setP = true; + } + if (I.z_index) { + I.z_index = false; + this.element.style.zIndex = O.z_index; + } + if (I.header) { + I.header = false; + this.set("show_header", !!O.header); + if (O.header) build_header.call(this); + } + if (I.footer) { + I.footer = false; + this.set("show_footer", !!O.footer); + if (O.footer) build_footer.call(this); + } + if (I.status) { + I.status = false; + status_timeout.call(this); + } + if (I.fixed) { + this.element.style.position = O.fixed ? "fixed" : "absolute"; + setP = true; + } + if (I.active) { + I.active = false; + TK.toggle_class(this.element, "toolkit-active", O.active); + } + if (I.shrink) { + I.shrink = false; + this.options.maximize.y = false; + TK.toggle_class(this.element, "toolkit-shrinked", O.shrink); + } + if (I.draggable) { + I.draggable = false; + TK.toggle_class(this.element, "toolkit-draggable", O.draggable); + } + if (I.resizable) { + I.resizable = false; + TK.toggle_class(this.element, "toolkit-resizable", O.resizable); + } + if (I.content) { + I.content = false; + if (O.content) { + if (TK.Container.prototype.isPrototypeOf(O.content)) { + TK.set_content(this.content.element, ""); + this.append_child(O.content); + } else { + TK.set_content(this.content.element, O.content); + } + } + setD = true; + setP = true; + } + + if (setD) set_dimensions.call(this); + if (setP) set_position.call(this); + TK.Container.prototype.redraw.call(this); + }, + + set: function (key, value) { + var O = this.options; + var E = this.element; + + if (key == "maximize") { + if (value === false) value = this.options.maximize = {x: false, y: false}; + else if (value === true) value = this.options.maximize = {x: true, y: true}; + else value = Object.assign(this.options.maximize, value); + } + O[key] = value; + + switch (key) { + case "shrink": + O.maximize.y = false; + break; + case "minimize": + if (value) { + if (!this.options.container && E.parentElement) + O.container = E.parentElement; + E.remove(); + } else if (O.container) { + this.set("container", O.container) + } + break; + case "resizable": + this.Resize.set("active", value); + break; + + } + return TK.Container.prototype.set.call(this, key, value); + } +}); + +/** + * @member {TK.Icon} TK.Window#icon - A {@link TK.Icon} widget to display the window icon. + */ +TK.ChildWidget(TK.Window, "icon", { + create: TK.Icon, + map_options: { icon : "icon" }, + toggle_class: true, +}); +/** + * @member {TK.Label} TK.Window#title - A {@link TK.Label} to display the window title. + */ +TK.ChildWidget(TK.Window, "title", { + create: TK.Label, + default_options: { "class" : "toolkit-title" }, + map_options: { title : "label" }, + toggle_class: true, +}); +/** + * @member {TK.Label} TK.Window#status - A {@link TK.Label} to display the window status. + */ +TK.ChildWidget(TK.Window, "status", { + create: TK.Label, + default_options: { "class" : "toolkit-status" }, + map_options: { status : "label" }, + toggle_class: true, +}); +/** + * @member {TK.Button} TK.Window#close - The close button. + */ +/** + * @member {TK.Button} TK.Window#minimize - The minimize button. + */ +/** + * @member {TK.Button} TK.Window#maximize - The maximize button. + */ +/** + * @member {TK.Button} TK.Window#maximizevertical - The maximizevertical button. + */ +/** + * @member {TK.Button} TK.Window#maximizehorizontal - The maximizehorizontal button. + */ +/** + * @member {TK.Button} TK.Window#shrink - The shrink button. + */ + +var bfactory = function (name) { + TK.ChildWidget(TK.Window, name, { + create: TK.Button, + default_options: { + "class" : "toolkit-" + name, + "icon" : "window" + name, + }, + static_events: { + "click" : function (e) { (eval(name)).call(this.parent, e); }, + "mousedown" : function (e) { e.stopPropagation(); }, + }, + }); +} +var b = ["close", "minimize", "maximize", "maximizevertical", "maximizehorizontal", "shrink"]; + +b.map(bfactory); +/** + * @member {TK.Icon} TK.Window#size - A {@link TK.Icon} acting as handle for window resize. + */ +TK.ChildWidget(TK.Window, "size", { + create: TK.Icon, + default_options: { "icon" : "windowresize", "class" : "toolkit-size" }, +}); +/** + * @member {TK.Container} TK.Window#content - A {@link TK.Container} for the window content. + */ +TK.ChildWidget(TK.Window, "content", { + create: TK.Container, + toggle_class: true, + show: true, + default_options: { "class" : "toolkit-content" }, +}); +/** + * @member {TK.Container} TK.Window#header - The top header bar. + */ +TK.ChildWidget(TK.Window, "header", { + create: TK.Container, + toggle_class: true, + show: true, + default_options: { "class" : "toolkit-header" }, + static_events: { + "dblclick" : header_action, + }, + append: function () { build_header.call(this); this.element.appendChild(this.header.element); }, +}); +/** + * @member {TK.Container} TK.Window#footer - The bottom footer bar. + */ +TK.ChildWidget(TK.Window, "footer", { + create: TK.Container, + toggle_class: true, + show: false, + default_options: { "class" : "toolkit-footer" }, + append : function () { build_footer.call(this); this.element.appendChild(this.footer.element); }, +}); + +})(this, this.TK); diff --git a/share/web_surfaces/builtin/mixer-demo/img/ardour-icon.svg b/share/web_surfaces/builtin/protocol/ardour-icon.svg similarity index 100% rename from share/web_surfaces/builtin/mixer-demo/img/ardour-icon.svg rename to share/web_surfaces/builtin/protocol/ardour-icon.svg diff --git a/share/web_surfaces/builtin/protocol/index.html b/share/web_surfaces/builtin/protocol/index.html new file mode 100644 index 0000000000..22294674bf --- /dev/null +++ b/share/web_surfaces/builtin/protocol/index.html @@ -0,0 +1,19 @@ + + + + + Ardour WebSockets Protocol + + + + +
+
+ + +
+
+
+ + + diff --git a/share/web_surfaces/builtin/protocol/main.css b/share/web_surfaces/builtin/protocol/main.css new file mode 100644 index 0000000000..0a954f6875 --- /dev/null +++ b/share/web_surfaces/builtin/protocol/main.css @@ -0,0 +1,67 @@ +html { + height: 100%; +} + +body { + background: rgb(62,61,61); + color: rgb(248,248,242); + font-family: Helvetica, Arial, sans-serif; + height: 100%; + width: 100%; + position: fixed; + margin: 0; +} + +#main { + display: flex; + flex-direction: column; + height: 100%; +} + +#top { + display: flex; + align-items: center; + padding: 0.25em 0.5em; + box-shadow: 0px 0px 10px #000; + z-index: 10; +} + +#top > img { + height: 24px; +} + +#top > span { + margin-left: 12px; +} + +#log { + padding: 1em; + display: flex; + flex-direction: column; + flex-grow: 1; + overflow: scroll; + overflow-x: hidden; + background: rgba(0,0,0,0.4); +} + +#log pre { + margin: 0; + font-family: Menlo, monospace; + font-size: 1em; +} + +.message-in { + color: rgb(248,248,242); +} + +.message-out { + color: rgb(172,128,255); +} + +.info { + color: rgb(99,208,230); +} + +.error { + color: rgb(249,36,114); +} diff --git a/share/web_surfaces/builtin/protocol/main.js b/share/web_surfaces/builtin/protocol/main.js new file mode 100644 index 0000000000..d88fc6612e --- /dev/null +++ b/share/web_surfaces/builtin/protocol/main.js @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2020 Luciano Iam + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +import ArdourClient from '/shared/ardour.js'; + +(() => { + + const MAX_LOG_LINES = 1000; + + const ardour = new ArdourClient(); + + async function main () { + ardour.on('connected', (connected) => { + if (connected) { + log('Client connected', 'info'); + } else { + log('Client disconnected', 'error'); + } + }); + + ardour.on('message', (msg, inbound) => { + if (inbound) { + log(`↙ ${msg}`, 'message-in'); + } else { + log(`↗ ${msg}`, 'message-out'); + } + }); + + await ardour.connect(); + + const manifest = await ardour.getSurfaceManifest(); + document.getElementById('manifest').innerHTML = manifest.name.toUpperCase() + + ' v' + manifest.version + ' — ' + manifest.description; + } + + function log (message, className) { + const output = document.getElementById('log'); + + if (output.childElementCount > MAX_LOG_LINES) { + output.removeChild(output.childNodes[0]); + } + + const pre = document.createElement('pre'); + pre.innerHTML = message; + pre.className = className; + + output.appendChild(pre); + output.scrollTop = output.scrollHeight; + } + + main(); + +})(); diff --git a/share/web_surfaces/builtin/protocol/manifest.xml b/share/web_surfaces/builtin/protocol/manifest.xml new file mode 100644 index 0000000000..8775c48dfb --- /dev/null +++ b/share/web_surfaces/builtin/protocol/manifest.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/share/web_surfaces/builtin/transport/manifest.xml b/share/web_surfaces/builtin/transport/manifest.xml index 93d7fcd075..e45b147bcd 100644 --- a/share/web_surfaces/builtin/transport/manifest.xml +++ b/share/web_surfaces/builtin/transport/manifest.xml @@ -1,6 +1,6 @@ - +