| // 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; |
| } |
| }; |