blob: cbddddbdaafc08dde2f4c6f1558401040a28f2cc [file] [log] [blame]
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Class definition for a rounded corner panel.
* @supported IE 6.0+, Safari 2.0+, Firefox 1.5+, Opera 9.2+.
* @see ../demos/roundedpanel.html
*/
goog.provide('goog.ui.BaseRoundedPanel');
goog.provide('goog.ui.CssRoundedPanel');
goog.provide('goog.ui.GraphicsRoundedPanel');
goog.provide('goog.ui.RoundedPanel');
goog.provide('goog.ui.RoundedPanel.Corner');
goog.require('goog.asserts');
goog.require('goog.dom');
goog.require('goog.dom.classlist');
goog.require('goog.graphics');
goog.require('goog.graphics.Path');
goog.require('goog.graphics.SolidFill');
goog.require('goog.graphics.Stroke');
goog.require('goog.math');
goog.require('goog.math.Coordinate');
goog.require('goog.style');
goog.require('goog.ui.Component');
goog.require('goog.userAgent');
/**
* Factory method that returns an instance of a BaseRoundedPanel.
* @param {number} radius The radius of the rounded corner(s), in pixels.
* @param {number} borderWidth The thickness of the border, in pixels.
* @param {string} borderColor The border color of the panel.
* @param {string=} opt_backgroundColor The background color of the panel.
* @param {number=} opt_corners The corners of the panel to be rounded. Any
* corners not specified will be rendered as square corners. Will default
* to all square corners if not specified.
* @param {goog.dom.DomHelper=} opt_domHelper The DOM helper object for the
* document we want to render in.
* @return {!goog.ui.BaseRoundedPanel} An instance of a
* goog.ui.BaseRoundedPanel subclass.
*/
goog.ui.RoundedPanel.create = function(radius,
borderWidth,
borderColor,
opt_backgroundColor,
opt_corners,
opt_domHelper) {
// This variable checks for the presence of Safari 3.0+ or Gecko 1.9+,
// which can leverage special CSS styles to create rounded corners.
var isCssReady = goog.userAgent.WEBKIT &&
goog.userAgent.isVersionOrHigher('500') ||
goog.userAgent.GECKO && goog.userAgent.isVersionOrHigher('1.9a');
if (isCssReady) {
// Safari 3.0+ and Firefox 3.0+ support this instance.
return new goog.ui.CssRoundedPanel(
radius,
borderWidth,
borderColor,
opt_backgroundColor,
opt_corners,
opt_domHelper);
} else {
return new goog.ui.GraphicsRoundedPanel(
radius,
borderWidth,
borderColor,
opt_backgroundColor,
opt_corners,
opt_domHelper);
}
};
/**
* Enum for specifying which corners to render.
* @enum {number}
*/
goog.ui.RoundedPanel.Corner = {
NONE: 0,
BOTTOM_LEFT: 2,
TOP_LEFT: 4,
LEFT: 6, // BOTTOM_LEFT | TOP_LEFT
TOP_RIGHT: 8,
TOP: 12, // TOP_LEFT | TOP_RIGHT
BOTTOM_RIGHT: 1,
BOTTOM: 3, // BOTTOM_LEFT | BOTTOM_RIGHT
RIGHT: 9, // TOP_RIGHT | BOTTOM_RIGHT
ALL: 15 // TOP | BOTTOM
};
/**
* CSS class name suffixes for the elements comprising the RoundedPanel.
* @enum {string}
* @private
*/
goog.ui.RoundedPanel.Classes_ = {
BACKGROUND: goog.getCssName('goog-roundedpanel-background'),
PANEL: goog.getCssName('goog-roundedpanel'),
CONTENT: goog.getCssName('goog-roundedpanel-content')
};
/**
* Base class for the hierarchy of RoundedPanel classes. Do not
* instantiate directly. Instead, call goog.ui.RoundedPanel.create().
* The HTML structure for the RoundedPanel is:
* <pre>
* - div (Contains the background and content. Class name: goog-roundedpanel)
* - div (Contains the background/rounded corners. Class name:
* goog-roundedpanel-bg)
* - div (Contains the content. Class name: goog-roundedpanel-content)
* </pre>
* @param {number} radius The radius of the rounded corner(s), in pixels.
* @param {number} borderWidth The thickness of the border, in pixels.
* @param {string} borderColor The border color of the panel.
* @param {string=} opt_backgroundColor The background color of the panel.
* @param {number=} opt_corners The corners of the panel to be rounded. Any
* corners not specified will be rendered as square corners. Will default
* to all square corners if not specified.
* @param {goog.dom.DomHelper=} opt_domHelper The DOM helper object for the
* document we want to render in.
* @extends {goog.ui.Component}
* @constructor
*/
goog.ui.BaseRoundedPanel = function(radius,
borderWidth,
borderColor,
opt_backgroundColor,
opt_corners,
opt_domHelper) {
goog.ui.Component.call(this, opt_domHelper);
/**
* The radius of the rounded corner(s), in pixels.
* @type {number}
* @private
*/
this.radius_ = radius;
/**
* The thickness of the border, in pixels.
* @type {number}
* @private
*/
this.borderWidth_ = borderWidth;
/**
* The border color of the panel.
* @type {string}
* @private
*/
this.borderColor_ = borderColor;
/**
* The background color of the panel.
* @type {?string}
* @private
*/
this.backgroundColor_ = opt_backgroundColor || null;
/**
* The corners of the panel to be rounded; defaults to
* goog.ui.RoundedPanel.Corner.NONE
* @type {number}
* @private
*/
this.corners_ = opt_corners || goog.ui.RoundedPanel.Corner.NONE;
};
goog.inherits(goog.ui.BaseRoundedPanel, goog.ui.Component);
goog.tagUnsealableClass(goog.ui.BaseRoundedPanel);
/**
* The element containing the rounded corners and background.
* @type {Element}
* @private
*/
goog.ui.BaseRoundedPanel.prototype.backgroundElement_;
/**
* The element containing the actual content.
* @type {Element}
* @private
*/
goog.ui.BaseRoundedPanel.prototype.contentElement_;
/**
* This method performs all the necessary DOM manipulation to create the panel.
* Overrides {@link goog.ui.Component#decorateInternal}.
* @param {Element} element The element to decorate.
* @protected
* @override
*/
goog.ui.BaseRoundedPanel.prototype.decorateInternal = function(element) {
goog.ui.BaseRoundedPanel.superClass_.decorateInternal.call(this, element);
goog.dom.classlist.add(goog.asserts.assert(this.getElement()),
goog.ui.RoundedPanel.Classes_.PANEL);
// Create backgroundElement_, and add it to the DOM.
this.backgroundElement_ = this.getDomHelper().createElement('div');
this.backgroundElement_.className = goog.ui.RoundedPanel.Classes_.BACKGROUND;
this.getElement().appendChild(this.backgroundElement_);
// Set contentElement_ by finding a child node within element_ with the
// proper class name. If none exists, create it and add it to the DOM.
this.contentElement_ = goog.dom.getElementsByTagNameAndClass(
null, goog.ui.RoundedPanel.Classes_.CONTENT, this.getElement())[0];
if (!this.contentElement_) {
this.contentElement_ = this.getDomHelper().createDom('div');
this.contentElement_.className = goog.ui.RoundedPanel.Classes_.CONTENT;
this.getElement().appendChild(this.contentElement_);
}
};
/** @override */
goog.ui.BaseRoundedPanel.prototype.disposeInternal = function() {
if (this.backgroundElement_) {
this.getDomHelper().removeNode(this.backgroundElement_);
this.backgroundElement_ = null;
}
this.contentElement_ = null;
goog.ui.BaseRoundedPanel.superClass_.disposeInternal.call(this);
};
/**
* Returns the DOM element containing the actual content.
* @return {Element} The element containing the actual content (null if none).
* @override
*/
goog.ui.BaseRoundedPanel.prototype.getContentElement = function() {
return this.contentElement_;
};
/**
* RoundedPanel class specifically for browsers that support CSS attributes
* for elements with rounded borders (ex. Safari 3.0+, Firefox 3.0+). Do not
* instantiate directly. Instead, call goog.ui.RoundedPanel.create().
* @param {number} radius The radius of the rounded corner(s), in pixels.
* @param {number} borderWidth The thickness of the border, in pixels.
* @param {string} borderColor The border color of the panel.
* @param {string=} opt_backgroundColor The background color of the panel.
* @param {number=} opt_corners The corners of the panel to be rounded. Any
* corners not specified will be rendered as square corners. Will
* default to all square corners if not specified.
* @param {goog.dom.DomHelper=} opt_domHelper The DOM helper object for the
* document we want to render in.
* @extends {goog.ui.BaseRoundedPanel}
* @constructor
* @final
*/
goog.ui.CssRoundedPanel = function(radius,
borderWidth,
borderColor,
opt_backgroundColor,
opt_corners,
opt_domHelper) {
goog.ui.BaseRoundedPanel.call(this,
radius,
borderWidth,
borderColor,
opt_backgroundColor,
opt_corners,
opt_domHelper);
};
goog.inherits(goog.ui.CssRoundedPanel, goog.ui.BaseRoundedPanel);
/**
* This method performs all the necessary DOM manipulation to create the panel.
* Overrides {@link goog.ui.Component#decorateInternal}.
* @param {Element} element The element to decorate.
* @protected
* @override
*/
goog.ui.CssRoundedPanel.prototype.decorateInternal = function(element) {
goog.ui.CssRoundedPanel.superClass_.decorateInternal.call(this, element);
// Set the border width and background color, if needed.
this.backgroundElement_.style.border = this.borderWidth_ +
'px solid ' +
this.borderColor_;
if (this.backgroundColor_) {
this.backgroundElement_.style.backgroundColor = this.backgroundColor_;
}
// Set radii of the appropriate rounded corners.
if (this.corners_ == goog.ui.RoundedPanel.Corner.ALL) {
var styleName = this.getStyle_(goog.ui.RoundedPanel.Corner.ALL);
this.backgroundElement_.style[styleName] = this.radius_ + 'px';
} else {
var topLeftRadius =
this.corners_ & goog.ui.RoundedPanel.Corner.TOP_LEFT ?
this.radius_ :
0;
var cornerStyle = this.getStyle_(goog.ui.RoundedPanel.Corner.TOP_LEFT);
this.backgroundElement_.style[cornerStyle] = topLeftRadius + 'px';
var topRightRadius =
this.corners_ & goog.ui.RoundedPanel.Corner.TOP_RIGHT ?
this.radius_ :
0;
cornerStyle = this.getStyle_(goog.ui.RoundedPanel.Corner.TOP_RIGHT);
this.backgroundElement_.style[cornerStyle] = topRightRadius + 'px';
var bottomRightRadius =
this.corners_ & goog.ui.RoundedPanel.Corner.BOTTOM_RIGHT ?
this.radius_ :
0;
cornerStyle = this.getStyle_(goog.ui.RoundedPanel.Corner.BOTTOM_RIGHT);
this.backgroundElement_.style[cornerStyle] = bottomRightRadius + 'px';
var bottomLeftRadius =
this.corners_ & goog.ui.RoundedPanel.Corner.BOTTOM_LEFT ?
this.radius_ :
0;
cornerStyle = this.getStyle_(goog.ui.RoundedPanel.Corner.BOTTOM_LEFT);
this.backgroundElement_.style[cornerStyle] = bottomLeftRadius + 'px';
}
};
/**
* This method returns the CSS style based on the corner of the panel, and the
* user-agent.
* @param {number} corner The corner whose style name to retrieve.
* @private
* @return {string} The CSS style based on the specified corner.
*/
goog.ui.CssRoundedPanel.prototype.getStyle_ = function(corner) {
// Determine the proper corner to work with.
var cssCorner, suffixLeft, suffixRight;
if (goog.userAgent.WEBKIT) {
suffixLeft = 'Left';
suffixRight = 'Right';
} else {
suffixLeft = 'left';
suffixRight = 'right';
}
switch (corner) {
case goog.ui.RoundedPanel.Corner.ALL:
cssCorner = '';
break;
case goog.ui.RoundedPanel.Corner.TOP_LEFT:
cssCorner = 'Top' + suffixLeft;
break;
case goog.ui.RoundedPanel.Corner.TOP_RIGHT:
cssCorner = 'Top' + suffixRight;
break;
case goog.ui.RoundedPanel.Corner.BOTTOM_LEFT:
cssCorner = 'Bottom' + suffixLeft;
break;
case goog.ui.RoundedPanel.Corner.BOTTOM_RIGHT:
cssCorner = 'Bottom' + suffixRight;
break;
}
return goog.userAgent.WEBKIT ?
'WebkitBorder' + cssCorner + 'Radius' :
'MozBorderRadius' + cssCorner;
};
/**
* RoundedPanel class that uses goog.graphics to create the rounded corners.
* Do not instantiate directly. Instead, call goog.ui.RoundedPanel.create().
* @param {number} radius The radius of the rounded corner(s), in pixels.
* @param {number} borderWidth The thickness of the border, in pixels.
* @param {string} borderColor The border color of the panel.
* @param {string=} opt_backgroundColor The background color of the panel.
* @param {number=} opt_corners The corners of the panel to be rounded. Any
* corners not specified will be rendered as square corners. Will
* default to all square corners if not specified.
* @param {goog.dom.DomHelper=} opt_domHelper The DOM helper object for the
* document we want to render in.
* @extends {goog.ui.BaseRoundedPanel}
* @constructor
* @final
*/
goog.ui.GraphicsRoundedPanel = function(radius,
borderWidth,
borderColor,
opt_backgroundColor,
opt_corners,
opt_domHelper) {
goog.ui.BaseRoundedPanel.call(this,
radius,
borderWidth,
borderColor,
opt_backgroundColor,
opt_corners,
opt_domHelper);
};
goog.inherits(goog.ui.GraphicsRoundedPanel, goog.ui.BaseRoundedPanel);
/**
* A 4-element array containing the circle centers for the arcs in the
* bottom-left, top-left, top-right, and bottom-right corners, respectively.
* @type {Array<goog.math.Coordinate>}
* @private
*/
goog.ui.GraphicsRoundedPanel.prototype.arcCenters_;
/**
* A 4-element array containing the start coordinates for rendering the arcs
* in the bottom-left, top-left, top-right, and bottom-right corners,
* respectively.
* @type {Array<goog.math.Coordinate>}
* @private
*/
goog.ui.GraphicsRoundedPanel.prototype.cornerStarts_;
/**
* A 4-element array containing the arc end angles for the bottom-left,
* top-left, top-right, and bottom-right corners, respectively.
* @type {Array<number>}
* @private
*/
goog.ui.GraphicsRoundedPanel.prototype.endAngles_;
/**
* Graphics object for rendering the background.
* @type {goog.graphics.AbstractGraphics}
* @private
*/
goog.ui.GraphicsRoundedPanel.prototype.graphics_;
/**
* A 4-element array containing the rounded corner radii for the bottom-left,
* top-left, top-right, and bottom-right corners, respectively.
* @type {Array<number>}
* @private
*/
goog.ui.GraphicsRoundedPanel.prototype.radii_;
/**
* A 4-element array containing the arc start angles for the bottom-left,
* top-left, top-right, and bottom-right corners, respectively.
* @type {Array<number>}
* @private
*/
goog.ui.GraphicsRoundedPanel.prototype.startAngles_;
/**
* Thickness constant used as an offset to help determine where to start
* rendering.
* @type {number}
* @private
*/
goog.ui.GraphicsRoundedPanel.BORDER_WIDTH_FACTOR_ = 1 / 2;
/**
* This method performs all the necessary DOM manipulation to create the panel.
* Overrides {@link goog.ui.Component#decorateInternal}.
* @param {Element} element The element to decorate.
* @protected
* @override
*/
goog.ui.GraphicsRoundedPanel.prototype.decorateInternal =
function(element) {
goog.ui.GraphicsRoundedPanel.superClass_.decorateInternal.call(this,
element);
// Calculate the points and angles for creating the rounded corners. Then
// instantiate a Graphics object for drawing purposes.
var elementSize = goog.style.getSize(this.getElement());
this.calculateArcParameters_(elementSize);
this.graphics_ = goog.graphics.createGraphics(
/** @type {number} */ (elementSize.width),
/** @type {number} */ (elementSize.height),
/** @type {number} */ (elementSize.width),
/** @type {number} */ (elementSize.height),
this.getDomHelper());
this.graphics_.createDom();
// Create the path, starting from the bottom-right corner, moving clockwise.
// End with the top-right corner.
var path = new goog.graphics.Path();
for (var i = 0; i < 4; i++) {
if (this.radii_[i]) {
// If radius > 0, draw an arc, moving to the first point and drawing
// a line to the others.
var cx = this.arcCenters_[i].x;
var cy = this.arcCenters_[i].y;
var rx = this.radii_[i];
var ry = rx;
var fromAngle = this.startAngles_[i];
var extent = this.endAngles_[i] - fromAngle;
var startX = cx + goog.math.angleDx(fromAngle, rx);
var startY = cy + goog.math.angleDy(fromAngle, ry);
if (i > 0) {
var currentPoint = path.getCurrentPoint();
if (!currentPoint || startX != currentPoint[0] ||
startY != currentPoint[1]) {
path.lineTo(startX, startY);
}
} else {
path.moveTo(startX, startY);
}
path.arcTo(rx, ry, fromAngle, extent);
} else if (i == 0) {
// If we're just starting out (ie. i == 0), move to the starting point.
path.moveTo(this.cornerStarts_[i].x,
this.cornerStarts_[i].y);
} else {
// Otherwise, draw a line to the starting point.
path.lineTo(this.cornerStarts_[i].x,
this.cornerStarts_[i].y);
}
}
// Close the path, create a stroke object, and fill the enclosed area, if
// needed. Then render the path.
path.close();
var stroke = this.borderWidth_ ?
new goog.graphics.Stroke(this.borderWidth_, this.borderColor_) :
null;
var fill = this.backgroundColor_ ?
new goog.graphics.SolidFill(this.backgroundColor_, 1) :
null;
this.graphics_.drawPath(path, stroke, fill);
this.graphics_.render(this.backgroundElement_);
};
/** @override */
goog.ui.GraphicsRoundedPanel.prototype.disposeInternal = function() {
goog.ui.GraphicsRoundedPanel.superClass_.disposeInternal.call(this);
this.graphics_.dispose();
delete this.graphics_;
delete this.radii_;
delete this.cornerStarts_;
delete this.arcCenters_;
delete this.startAngles_;
delete this.endAngles_;
};
/**
* Calculates the start coordinates, circle centers, and angles, for the rounded
* corners at each corner of the panel.
* @param {goog.math.Size} elementSize The size of element_.
* @private
*/
goog.ui.GraphicsRoundedPanel.prototype.calculateArcParameters_ =
function(elementSize) {
// Initialize the arrays containing the key points and angles.
this.radii_ = [];
this.cornerStarts_ = [];
this.arcCenters_ = [];
this.startAngles_ = [];
this.endAngles_ = [];
// Set the start points, circle centers, and angles for the bottom-right,
// bottom-left, top-left and top-right corners, in that order.
var angleInterval = 90;
var borderWidthOffset = this.borderWidth_ *
goog.ui.GraphicsRoundedPanel.BORDER_WIDTH_FACTOR_;
var radius, xStart, yStart, xCenter, yCenter, startAngle, endAngle;
for (var i = 0; i < 4; i++) {
var corner = Math.pow(2, i); // Determines which corner we're dealing with.
var isLeft = corner & goog.ui.RoundedPanel.Corner.LEFT;
var isTop = corner & goog.ui.RoundedPanel.Corner.TOP;
// Calculate the radius and the start coordinates.
radius = corner & this.corners_ ? this.radius_ : 0;
switch (corner) {
case goog.ui.RoundedPanel.Corner.BOTTOM_LEFT:
xStart = borderWidthOffset + radius;
yStart = elementSize.height - borderWidthOffset;
break;
case goog.ui.RoundedPanel.Corner.TOP_LEFT:
xStart = borderWidthOffset;
yStart = radius + borderWidthOffset;
break;
case goog.ui.RoundedPanel.Corner.TOP_RIGHT:
xStart = elementSize.width - radius - borderWidthOffset;
yStart = borderWidthOffset;
break;
case goog.ui.RoundedPanel.Corner.BOTTOM_RIGHT:
xStart = elementSize.width - borderWidthOffset;
yStart = elementSize.height - radius - borderWidthOffset;
break;
}
// Calculate the circle centers and start/end angles.
xCenter = isLeft ?
radius + borderWidthOffset :
elementSize.width - radius - borderWidthOffset;
yCenter = isTop ?
radius + borderWidthOffset :
elementSize.height - radius - borderWidthOffset;
startAngle = angleInterval * i;
endAngle = startAngle + angleInterval;
// Append the radius, angles, and coordinates to their arrays.
this.radii_[i] = radius;
this.cornerStarts_[i] = new goog.math.Coordinate(xStart, yStart);
this.arcCenters_[i] = new goog.math.Coordinate(xCenter, yCenter);
this.startAngles_[i] = startAngle;
this.endAngles_[i] = endAngle;
}
};