@qooxdoo/framework
Version:
The JS Framework for Coders
420 lines (370 loc) • 17.6 kB
JavaScript
/* ************************************************************************
qooxdoo - the new era of web development
http://qooxdoo.org
Copyright:
2004-2010 1&1 Internet AG, Germany, http://www.1und1.de
License:
MIT: https://opensource.org/licenses/MIT
See the LICENSE file in the project's top-level directory for details.
Authors:
* Martin Wittemann (martinwittemann)
************************************************************************ */
/**
* Mixin for the linear background gradient CSS property.
* This mixin is usually used by {@link qx.ui.decoration.Decorator}.
*
* Keep in mind that this is not supported by all browsers:
*
* * Safari 4.0+
* * Chrome 4.0+
* * Firefox 3.6+
* * Opera 11.1+
* * IE 10+
* * IE 5.5+ (with limitations)
*
* For IE 5.5 to IE 8,this class uses the filter rules to create the gradient. This
* has some limitations: The start and end position property can not be used. For
* more details, see the original documentation:
* http://msdn.microsoft.com/en-us/library/ms532997(v=vs.85).aspx
*
* For IE9, we create a gradient in a canvas element and render this gradient
* as background image. Due to restrictions in the <code>background-image</code>
* css property, we can not allow negative start values in that case.
*
* It is possible to define multiple background gradients by setting an
* array containing the needed values as the property value.
* In case multiple values are specified, the values of the properties
* are repeated until all match in length. It is not possible to define
* multiple background gradients when falling back to filter rules (IE5.5 to IE8).
*
* An example:
* <pre class="javascript">
* 'my-decorator': {
* style: {
* startColor:['rgba(255, 0, 0, 0.5)', 'rgba(0, 255, 0, 0.5)', 'rgba(0, 0, 255, 0.5)'],
* endColor: 'rgba(255, 255, 255, 0.2)',
* orientation: ['horizontal', 'vertical']
* }
* }
* </pre>
* which is the same as:
* <pre class="javascript">
* 'my-decorator': {
* style: {
* startColor: ['rgba(255, 0, 0, 0.5)', 'rgba(0, 255, 0, 0.5)', 'rgba(0, 0, 255, 0.5)'],
* endColor: ['rgba(255, 255, 255, 0.2)', 'rgba(255, 255, 255, 0.2)', 'rgba(255, 255, 255, 0.2)'],
* orientation: ['horizontal', 'vertical', 'horizontal']
* }
* }
* </pre>
*/
qx.Mixin.define("qx.ui.decoration.MLinearBackgroundGradient",
{
properties :
{
/**
* Start color of the background gradient.
* Note that alpha transparency (rgba) is not supported in IE 8.
*/
startColor :
{
nullable : true,
apply : "_applyLinearBackgroundGradient"
},
/**
* End color of the background gradient.
* Note that alpha transparency (rgba) is not supported in IE 8.
*/
endColor :
{
nullable : true,
apply : "_applyLinearBackgroundGradient"
},
/** The orientation of the gradient. */
orientation :
{
init : "vertical",
apply : "_applyLinearBackgroundGradient"
},
/** Position in percent where to start the color. */
startColorPosition :
{
init : 0,
apply : "_applyLinearBackgroundGradient"
},
/** Position in percent where to start the color. */
endColorPosition :
{
init : 100,
apply : "_applyLinearBackgroundGradient"
},
/** Defines if the given positions are in % or px.*/
colorPositionUnit :
{
init : "%",
apply : "_applyLinearBackgroundGradient"
},
/** Property group to set the start color including its start position. */
gradientStart :
{
group : ["startColor", "startColorPosition"],
mode : "shorthand"
},
/** Property group to set the end color including its end position. */
gradientEnd :
{
group : ["endColor", "endColorPosition"],
mode : "shorthand"
}
},
members :
{
/**
* Takes a styles map and adds the linear background styles in place to the
* given map. This is the needed behavior for
* {@link qx.ui.decoration.Decorator}.
*
* @param styles {Map} A map to add the styles.
*/
_styleLinearBackgroundGradient : function(styles) {
var backgroundStyle = [];
if(!this.getStartColor() || !this.getEndColor()) {
return;
}
var styleImpl = this.__styleLinearBackgroundGradientAccordingToSpec;
if (qx.core.Environment.get("css.gradient.legacywebkit")) {
styleImpl = this.__styleLinearBackgroundGradientForLegacyWebkit;
} else if (qx.core.Environment.get("css.gradient.filter") &&
!qx.core.Environment.get("css.gradient.linear") && qx.core.Environment.get("css.borderradius")) {
styleImpl = this.__styleLinearBackgroundGradientWithCanvas;
} else if (qx.core.Environment.get("css.gradient.filter") &&
!qx.core.Environment.get("css.gradient.linear")) {
styleImpl = this.__styleLinearBackgroundGradientWithMSFilter;
}
var gradientProperties = ["startColor", "endColor", "colorPositionUnit", "orientation",
"startColorPosition", "endColorPosition"];
(function(startColors, endColors, units, orientations, startColorPositions, endColorPositions) {
for(var i=0;i<startColors.length;i++) {
var startColor = this.__getColor(startColors[i]);
var endColor = this.__getColor(endColors[i]);
var unit = units[i];
var orientation = orientations[i];
var startColorPosition = startColorPositions[i];
var endColorPosition = endColorPositions[i];
if(!styleImpl(startColor, endColor, unit, orientation, startColorPosition, endColorPosition, styles, backgroundStyle)) {
break;
}
}
if("background" in styles) {
if(!qx.lang.Type.isArray(styles['background'])) {
styles['background'] = [styles['background']];
}
} else {
styles['background'] = [];
}
var orderGradientsFront = 'getOrderGradientsFront' in this ? this.getOrderGradientsFront() : false;
var operation = orderGradientsFront ? Array.prototype.unshift : Array.prototype.push;
operation.apply(styles['background'], backgroundStyle);
}).apply(this, this._getExtendedPropertyValueArrays(gradientProperties));
},
/**
* Compute CSS rules to style the background with gradients.
* This can be called multiple times and SHOULD layer the gradients on top of each other and on top of existing backgrounds.
* Legacy implementation for old WebKit browsers (Chrome < 10).
*
* @param startColor {Color} The color to start the gradient with
* @param endColor {Color} The color to end the gradient with
* @param unit {Color} The unit in which startColorPosition and endColorPosition are measured
* @param orientation {String} Either 'horizontal' or 'vertical'
* @param startColorPosition {Number} The position of the gradient’s starting point, measured in `unit` units along the `orientation` axis from top or left
* @param endColorPosition {Number} The position of the gradient’s ending point, measured in `unit` units along the `orientation` axis from top or left
* @param styles {Map} The complete styles currently poised to be applied by decorators. Should not be written to in this method (use `backgroundStyle` for that)
* @param backgroundStyle {Map} This method should push new background styles onto this array.
*
* @return {Boolean} Whether this implementation supports multiple gradients atop each other (true).
*/
__styleLinearBackgroundGradientForLegacyWebkit: function(startColor, endColor, unit, orientation, startColorPosition, endColorPosition, styles, backgroundStyle) {
// webkit uses px values if non are given
unit = unit === "px" ? "" : unit;
if (orientation == "horizontal") {
var startPos = startColorPosition + unit +" 0" + unit;
var endPos = endColorPosition + unit + " 0" + unit;
} else {
var startPos = "0" + unit + " " + startColorPosition + unit;
var endPos = "0" + unit +" " + endColorPosition + unit;
}
var color =
"from(" + startColor +
"),to(" + endColor + ")";
backgroundStyle.push("-webkit-gradient(linear," + startPos + "," + endPos + "," + color + ")");
return true;
},
/**
* Compute CSS rules to style the background with gradients.
* This can be called multiple times and SHOULD layer the gradients on top of each other and on top of existing backgrounds.
* IE9 canvas solution.
*
* @param startColor {Color} The color to start the gradient with
* @param endColor {Color} The color to end the gradient with
* @param unit {Color} The unit in which startColorPosition and endColorPosition are measured
* @param orientation {String} Either 'horizontal' or 'vertical'
* @param startColorPosition {Number} The position of the gradient’s starting point, measured in `unit` units along the `orientation` axis from top or left
* @param endColorPosition {Number} The position of the gradient’s ending point, measured in `unit` units along the `orientation` axis from top or left
* @param styles {Map} The complete styles currently poised to be applied by decorators. Should not be written to in this method (use `backgroundStyle` for that)
* @param backgroundStyle {Map} This method should push new background styles onto this array.
*
* @return {Boolean} Whether this implementation supports multiple gradients atop each other (true).
*/
__styleLinearBackgroundGradientWithCanvas: function me(startColor, endColor, unit, orientation, startColorPosition, endColorPosition, styles, backgroundStyle) {
if (!me.__canvas) {
me.__canvas = document.createElement("canvas");
}
var isVertical = orientation == "vertical";
var height = isVertical ? 200 : 1;
var width = isVertical ? 1 : 200;
var range = Math.max(100, endColorPosition - startColorPosition);
// use the px difference as dimension
if (unit === "px") {
if (isVertical) {
height = Math.max(height, endColorPosition - startColorPosition);
} else {
width = Math.max(width, endColorPosition - startColorPosition);
}
} else {
if (isVertical) {
height = Math.max(height, (endColorPosition - startColorPosition) * 2);
} else {
width = Math.max(width, (endColorPosition - startColorPosition) * 2);
}
}
me.__canvas.width = width;
me.__canvas.height = height;
var ctx = me.__canvas.getContext('2d');
if (isVertical) {
var lingrad = ctx.createLinearGradient(0, 0, 0, height);
} else {
var lingrad = ctx.createLinearGradient(0, 0, width, 0);
}
// don't allow negative start values
if (unit === "%") {
lingrad.addColorStop(Math.max(0, startColorPosition) / range, startColor);
lingrad.addColorStop(endColorPosition / range, endColor);
} else {
var comp = isVertical ? height : width;
lingrad.addColorStop(Math.max(0, startColorPosition) / comp, startColor);
lingrad.addColorStop(endColorPosition / comp, endColor);
}
//Clear the rect before drawing to allow for semitransparent colors
ctx.clearRect(0, 0, width, height);
ctx.fillStyle = lingrad;
ctx.fillRect(0, 0, width, height);
var size;
if (unit === "%") {
size = isVertical ? "100% " + range + "%" : range + "% 100%";
} else {
size = isVertical ? height + "px 100%" : "100% " + width + "px";
}
backgroundStyle.push("url(" + me.__canvas.toDataURL() + ") " + size);
return true;
},
/**
* Compute CSS rules to style the background with gradients.
* This can be called multiple times and SHOULD layer the gradients on top of each other and on top of existing backgrounds.
* Old IE filter fallback.
*
* @param startColor {Color} The color to start the gradient with
* @param endColor {Color} The color to end the gradient with
* @param unit {Color} The unit in which startColorPosition and endColorPosition are measured
* @param orientation {String} Either 'horizontal' or 'vertical'
* @param startColorPosition {Number} The position of the gradient’s starting point, measured in `unit` units along the `orientation` axis from top or left
* @param endColorPosition {Number} The position of the gradient’s ending point, measured in `unit` units along the `orientation` axis from top or left
* @param styles {Map} The complete styles currently poised to be applied by decorators. Should not be written to in this method (use `backgroundStyle` for that). Note: this particular implementation will do that because it needs to change the `filter` property.
* @param backgroundStyle {Map} This method should push new background styles onto this array.
*
* @return {Boolean} Whether this implementation supports multiple gradients atop each other (false).
*/
__styleLinearBackgroundGradientWithMSFilter: function(startColor, endColor, unit, orientation, startColorPosition, endColorPosition, styles, backgroundStyle) {
var type = orientation == "horizontal" ? 1 : 0;
// convert rgb, hex3 and named colors to hex6
if (!qx.util.ColorUtil.isHex6String(startColor)) {
startColor = qx.util.ColorUtil.stringToRgb(startColor);
startColor = qx.util.ColorUtil.rgbToHexString(startColor);
}
if (!qx.util.ColorUtil.isHex6String(endColor)) {
endColor = qx.util.ColorUtil.stringToRgb(endColor);
endColor = qx.util.ColorUtil.rgbToHexString(endColor);
}
// get rid of the starting '#'
startColor = startColor.substring(1, startColor.length);
endColor = endColor.substring(1, endColor.length);
var value = "progid:DXImageTransform.Microsoft.Gradient" +
"(GradientType=" + type + ", " +
"StartColorStr='#FF" + startColor + "', " +
"EndColorStr='#FF" + endColor + "';)";
if (styles["filter"]) {
styles["filter"] += ", " + value;
} else {
styles["filter"] = value;
}
// Elements with transparent backgrounds will not receive receive pointer
// events if a Gradient filter is set.
if (!styles["background-color"] ||
styles["background-color"] == "transparent")
{
// We don't support alpha transparency for the gradient color stops
// so it doesn't matter which color we set here.
styles["background-color"] = "white";
}
return false;
},
/**
* Compute CSS rules to style the background with gradients.
* This can be called multiple times and SHOULD layer the gradients on top of each other and on top of existing backgrounds.
* Default implementation (uses spec-compliant syntax).
*
* @param startColor {Color} The color to start the gradient with
* @param endColor {Color} The color to end the gradient with
* @param unit {Color} The unit in which startColorPosition and endColorPosition are measured
* @param orientation {String} Either 'horizontal' or 'vertical'
* @param startColorPosition {Number} The position of the gradient’s starting point, measured in `unit` units along the `orientation` axis from top or left
* @param endColorPosition {Number} The position of the gradient’s ending point, measured in `unit` units along the `orientation` axis from top or left
* @param styles {Map} The complete styles currently poised to be applied by decorators. Should not be written to in this method (use `backgroundStyle` for that)
* @param backgroundStyle {Map} This method should push new background styles onto this array.
*
* @return {Boolean} Whether this implementation supports multiple gradients atop each other (true).
*/
__styleLinearBackgroundGradientAccordingToSpec: function(startColor, endColor, unit, orientation, startColorPosition, endColorPosition, styles, backgroundStyle) {
// WebKit, Opera and Gecko interpret 0deg as "to right"
var deg = orientation == "horizontal" ? 0 : 270;
var start = startColor + " " + startColorPosition + unit;
var end = endColor + " " + endColorPosition + unit;
var prefixedName = qx.core.Environment.get("css.gradient.linear");
// Browsers supporting the unprefixed implementation interpret 0deg as
// "to top" as defined by the spec [BUG #6513]
if (prefixedName === "linear-gradient") {
deg = orientation == "horizontal" ? deg + 90 : deg - 90;
}
backgroundStyle.push(prefixedName + "(" + deg + "deg, " + start + "," + end + ")");
return true;
},
/**
* Helper to get a resolved color from a name
* @param color {String} The color name
* @return {Map} The resolved color
*/
__getColor : function(color) {
return qx.core.Environment.get("qx.theme") ?
qx.theme.manager.Color.getInstance().resolve(color) : color;
},
// property apply
_applyLinearBackgroundGradient : function()
{
if (qx.core.Environment.get("qx.debug"))
{
if (this._isInitialized()) {
throw new Error("This decorator is already in-use. Modification is not possible anymore!");
}
}
}
}
});