//////////////////////////////////////////////////////////////////////////////// // // Licensed to the Apache Software Foundation (ASF) under one or more // contributor license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright ownership. // The ASF licenses this file to You 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. // //////////////////////////////////////////////////////////////////////////////// package spark.components { import flash.display.GradientType; import flash.events.Event; import flash.geom.Matrix; import flash.text.TextLineMetrics; import mx.controls.listClasses.*; import mx.core.DPIClassification; import mx.core.FlexGlobals; import mx.core.IDataRenderer; import mx.core.IFlexDisplayObject; import mx.core.ILayoutElement; import mx.core.UIComponent; import mx.core.mx_internal; import mx.events.FlexEvent; import spark.components.supportClasses.InteractionState; import spark.components.supportClasses.InteractionStateDetector; import spark.components.supportClasses.StyleableTextField; use namespace mx_internal; //-------------------------------------- // Events //-------------------------------------- /** * Dispatched when the data property changes. * *

When you use a component as an item renderer, * the data property contains the data to display. * You can listen for this event and update the component * when the data property changes.

* * @eventType mx.events.FlexEvent.DATA_CHANGE * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ [Event(name="dataChange", type="mx.events.FlexEvent")] //-------------------------------------- // Styles //-------------------------------------- include "../styles/metadata/PaddingStyles.as" include "../styles/metadata/StyleableTextFieldTextStyles.as" /** * @copy spark.components.supportClasses.GroupBase#style:alternatingItemColors * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ [Style(name="alternatingItemColors", type="Array", arrayType="uint", format="Color", inherit="yes", theme="spark, mobile")] /** * @copy spark.components.supportClasses.GroupBase#style:chromeColor * * @default 0xCCCCCC * * @langversion 3.0 * @playerversion AIR 1.5 * @productversion Flex 4 */ [Style(name="chromeColor", type="uint", format="Color", inherit="yes", theme="spark, mobile")] /** * @copy spark.components.supportClasses.GroupBase#style:downColor * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ [Style(name="downColor", type="uint", format="Color", inherit="yes", theme="spark, mobile")] /** * @copy spark.components.supportClasses.GroupBase#style:focusColor * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ [Style(name="focusColor", type="uint", format="Color", inherit="yes", theme="spark, mobile")] /** * Number of pixels between the bottom border and the text component * of the item renderer. * * @default 5 * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ [Style(name="paddingBottom", type="Number", format="Length", inherit="no")] /** * Number of pixels between the top border and the text component * of the item renderer. * * @default 5 * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ [Style(name="paddingTop", type="Number", format="Length", inherit="no")] /** * @copy spark.components.supportClasses.GroupBase#style:rollOverColor * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ [Style(name="rollOverColor", type="uint", format="Color", inherit="yes")] /** * @copy spark.components.List#style:selectionColor * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ [Style(name="selectionColor", type="uint", format="Color", inherit="yes")] /** * @copy spark.components.supportClasses.GroupBase#style:symbolColor * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ [Style(name="symbolColor", type="uint", format="Color", inherit="yes", theme="spark,mobile")] /** * The vertical alignment of the content when it does not have * a one-to-one aspect ratio. * Possible values are "top", "center", * and "bottom". * * @default "center" * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ [Style(name="verticalAlign", type="String", enumeration="bottom,middle,top", inherit="no")] //-------------------------------------- // Excluded APIs //-------------------------------------- [Exclude(name="focusBlendMode", kind="style")] [Exclude(name="focusThickness", kind="style")] /** * The LabelItemRenderer class defines the default item renderer * for a list-based control in the mobile theme. * This is a simple item renderer with a single text component. * *

The item renderer creates a single StyleableTextField control * to display a String. * The name of the StyleableTextField control in the item renderer is labelDisplay. * Use the labelField property of the list-based control to specify * a field of the data item to display in the StyleableTextField control.

* *

To create a custom item renderer for use on mobile devices, * Adobe recommends that you create a new ActionScript item renderer * that extends this class.

* * @mxml * *

The <s:LabelItemRenderer> tag inherits all of the tag * attributes of its superclass and adds the following tag attributes:

* *
 *  <s:LabelItemRenderer
 *   Properties
 *    label=""
 * 
 *   Common Styles
 *    color="Theme dependent"
 *    downColor="Theme dependent"
 *    fontFamily="Theme dependent"
 *    fontSize="Theme dependent"
 *    fontStyle="normal"
 *    fontWeight="normal"
 *    leading="0"
 *    letterSpacing="0"
 *    paddingBottom="0"
 *    paddingLeft="0"
 *    paddingRight="0"
 *    paddingTop="5"
 *    rollOverColor="0xCEDBEF"
 *    selectionColor="0xB2B2B2"
 *    textAlign="left"
 *    textDecoration="none"
 *    textIndent="0"
 *    verticalAlign="center"
 *  
 *   Spark Styles
 *    alternatingItemColors="undefined"
 *    focusColor="0x70B2EE"
 *    symbolColor="0x000000"
 *  
 *   Mobile Styles
 *    alternatingItemColors="undefined"
 *    focusColor="0x70B2EE"
 *    symbolColor="0x000000"
 * 
 *    Events
 *    dataChange="No default"
 * 
 *  >
 *  
* * @see spark.components.IconItemRenderer * @see spark.components.List * @see mx.core.IDataRenderer * @see spark.components.IItemRenderer * @see spark.components.supportClasses.ItemRenderer * @includeExample examples/LabelItemRendererExample.mxml -noswf * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public class LabelItemRenderer extends UIComponent implements IDataRenderer, IItemRenderer { //-------------------------------------------------------------------------- // // Constructor // //-------------------------------------------------------------------------- /** * Constructor. * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function LabelItemRenderer() { super(); switch (applicationDPI) { case DPIClassification.DPI_320: { minHeight = 88; break; } case DPIClassification.DPI_240: { minHeight = 66; break; } default: { // default PPI160 minHeight = 44; break; } } interactionStateDetector = new InteractionStateDetector(this); interactionStateDetector.addEventListener(Event.CHANGE, interactionStateDetector_changeHandler); cacheAsBitmap = true; } //-------------------------------------------------------------------------- // // Private Properties // //-------------------------------------------------------------------------- /** * @private * Helper class to help determine when we are in the hovered or down states */ private var interactionStateDetector:InteractionStateDetector; /** * @private * Whether or not we're the last element in the list */ mx_internal var isLastItem:Boolean = false; //-------------------------------------------------------------------------- // // Overridden properties: UIComponent // //-------------------------------------------------------------------------- //---------------------------------- // baselinePosition //---------------------------------- /** * @private */ override public function get baselinePosition():Number { // The text styles aren't known until there is a parent. if (!parent) return NaN; return labelDisplay.baselinePosition; } //-------------------------------------------------------------------------- // // Public Properties // //-------------------------------------------------------------------------- //---------------------------------- // data //---------------------------------- /** * @private */ private var _data:Object; [Bindable("dataChange")] /** * The implementation of the data property * as defined by the IDataRenderer interface. * When set, it stores the value and invalidates the component * to trigger a relayout of the component. * * @see mx.core.IDataRenderer * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function get data():Object { return _data; } /** * @private */ public function set data(value:Object):void { _data = value; if (hasEventListener(FlexEvent.DATA_CHANGE)) dispatchEvent(new FlexEvent(FlexEvent.DATA_CHANGE)); } //---------------------------------- // down //---------------------------------- /** * @private * storage for the down property */ private var _down:Boolean = false; /** * Set to true when the user is pressing down on an item renderer. * * @default false * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ protected function get down():Boolean { return _down; } /** * @private */ protected function set down(value:Boolean):void { if (value == _down) return; _down = value; invalidateDisplayList(); } //---------------------------------- // hovered //---------------------------------- /** * @private * storage for the hovered property */ private var _hovered:Boolean = false; /** * Set to true when the user is hovered over the item renderer. * * @default false * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ protected function get hovered():Boolean { return _hovered; } /** * @private */ protected function set hovered(value:Boolean):void { if (value == _hovered) return; _hovered = value; invalidateDisplayList(); } //---------------------------------- // itemIndex //---------------------------------- /** * @private * storage for the itemIndex property */ private var _itemIndex:int; /** * @inheritDoc * * @default 0 * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function get itemIndex():int { return _itemIndex; } /** * @private */ public function set itemIndex(value:int):void { var wasLastItem:Boolean = isLastItem; var dataGroup:DataGroup = parent as DataGroup; isLastItem = (dataGroup && (value == dataGroup.numElements - 1)); // if whether or not we are the last item in the last has changed then // invalidate our display. note: even if our new index has not changed, // whether or not we're the last item may have so we perform this check // before the value == _itemIndex check below if (wasLastItem != isLastItem) invalidateDisplayList(); if (value == _itemIndex) return; _itemIndex = value; // only invalidateDisplayList() if this causes use to redraw which // is only if alternatingItemColors are defined (and technically also // only if we are not selected or down, etc..., but we'll ignore those // as this will shortcut 95% of the time anyways) if (getStyle("alternatingItemColors") !== undefined) invalidateDisplayList(); } //---------------------------------- // label //---------------------------------- /** * @private * Storage var for label */ private var _label:String = ""; /** * The text component used to * display the label data of the item renderer. * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ protected var labelDisplay:StyleableTextField; /** * @inheritDoc * * @default "" * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function get label():String { return _label; } /** * @private */ public function set label(value:String):void { if (value == _label) return; _label = value; // Push the label down into the labelTextField, // if it exists if (labelDisplay) { labelDisplay.text = _label; invalidateSize(); } } //---------------------------------- // showsCaret //---------------------------------- /** * @private * Storage for the showsCaret property */ private var _showsCaret:Boolean = false; /** * @inheritDoc * * @default false * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function get showsCaret():Boolean { return _showsCaret; } /** * @private */ public function set showsCaret(value:Boolean):void { if (value == _showsCaret) return; _showsCaret = value; invalidateDisplayList(); } //---------------------------------- // selected //---------------------------------- /** * @private * storage for the selected property */ private var _selected:Boolean = false; /** * @inheritDoc * * @default false * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function get selected():Boolean { return _selected; } /** * @private */ public function set selected(value:Boolean):void { if (value == _selected) return; _selected = value; invalidateDisplayList(); } //---------------------------------- // dragging //---------------------------------- /** * @private * Storage for the dragging property. */ private var _dragging:Boolean = false; /** * @inheritDoc * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function get dragging():Boolean { return _dragging; } /** * @private */ public function set dragging(value:Boolean):void { _dragging = value; } //---------------------------------- // authorDensity //---------------------------------- /** * Returns the DPI of the application. * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function get applicationDPI():Number { return FlexGlobals.topLevelApplication.applicationDPI; } //-------------------------------------------------------------------------- // // Overridden methods: UIComponent // //-------------------------------------------------------------------------- /** * @private */ override protected function createChildren():void { super.createChildren(); if (!labelDisplay) { createLabelDisplay(); labelDisplay.text = _label; } } /** * Creates the labelDisplay component * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ protected function createLabelDisplay():void { labelDisplay = StyleableTextField(createInFontContext(StyleableTextField)); labelDisplay.styleName = this; labelDisplay.editable = false; labelDisplay.selectable = false; labelDisplay.multiline = false; labelDisplay.wordWrap = false; addChild(labelDisplay); } /** * Destroys the labelDisplay component * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ protected function destroyLabelDisplay():void { removeChild(labelDisplay); labelDisplay = null; } /** * @private */ override protected function measure():void { super.measure(); if (labelDisplay) { // reset text if it was truncated before. if (labelDisplay.isTruncated) labelDisplay.text = label; var horizontalPadding:Number = getStyle("paddingLeft") + getStyle("paddingRight"); var verticalPadding:Number = getStyle("paddingTop") + getStyle("paddingBottom"); // Text respects padding right, left, top, and bottom labelDisplay.commitStyles(); measuredWidth = getElementPreferredWidth(labelDisplay) + horizontalPadding; // We only care about the "real" ascent measuredHeight = getElementPreferredHeight(labelDisplay) + verticalPadding; } measuredMinWidth = 0; } /** * @private */ override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void { // clear the graphics before calling super.updateDisplayList() graphics.clear(); super.updateDisplayList(unscaledWidth, unscaledHeight); drawBackground(unscaledWidth, unscaledHeight); layoutContents(unscaledWidth, unscaledHeight); } //-------------------------------------------------------------------------- // // Methods // //-------------------------------------------------------------------------- /** * Renders a background for the item renderer. * *

This method, along with layoutContents(), is called * by the updateDisplayList() method.

* *

This method draws the background and the outline for this item renderer. * It knows how to appropriately handle the selected, down, or caretted states. * However, when alternatingItemColors is set to undefined, * the default background is transparent. * Override this method to change the appearance of the background of * the item renderer.

* * @param unscaledWidth Specifies the width of the component, in pixels, * in the component's coordinates, regardless of the value of the * scaleX property of the component. * * @param unscaledHeight Specifies the height of the component, in pixels, * in the component's coordinates, regardless of the value of the * scaleY property of the component. * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ protected function drawBackground(unscaledWidth:Number, unscaledHeight:Number):void { // figure out backgroundColor var backgroundColor:*; var downColor:* = getStyle("downColor"); var drawBackground:Boolean = true; var opaqueBackgroundColor:* = undefined; if (down && downColor !== undefined) { backgroundColor = downColor; } else if (selected) { backgroundColor = getStyle("selectionColor"); } else if (hovered) { backgroundColor = getStyle("rollOverColor"); } else if (showsCaret) { backgroundColor = getStyle("selectionColor"); } else { var alternatingColors:Array; var alternatingColorsStyle:Object = getStyle("alternatingItemColors"); if (alternatingColorsStyle) alternatingColors = (alternatingColorsStyle is Array) ? (alternatingColorsStyle as Array) : [alternatingColorsStyle]; if (alternatingColors && alternatingColors.length > 0) { // translate these colors into uints styleManager.getColorNames(alternatingColors); backgroundColor = alternatingColors[itemIndex % alternatingColors.length]; } else { // don't draw background if it is the contentBackgroundColor. The // list skin handles the background drawing for us. drawBackground = false; } } // draw backgroundColor // the reason why we draw it in the case of drawBackground == 0 is for // mouse hit testing purposes graphics.beginFill(backgroundColor, drawBackground ? 1 : 0); graphics.lineStyle(); graphics.drawRect(0, 0, unscaledWidth, unscaledHeight); graphics.endFill(); // Selected and down states have a gradient overlay as well // as different separators colors/alphas if (selected || down) { var colors:Array = [0x000000, 0x000000 ]; var alphas:Array = [.2, .1]; var ratios:Array = [0, 255]; var matrix:Matrix = new Matrix(); // gradient overlay matrix.createGradientBox(unscaledWidth, unscaledHeight, Math.PI / 2, 0, 0 ); graphics.beginGradientFill(GradientType.LINEAR, colors, alphas, ratios, matrix); graphics.drawRect(0, 0, unscaledWidth, unscaledHeight); graphics.endFill(); } else if (drawBackground) { // If our background is a solid color, use it as the opaqueBackground property // for this renderer. This makes scrolling considerably faster. opaqueBackgroundColor = backgroundColor; } // Draw the separator for the item renderer drawBorder(unscaledWidth, unscaledHeight); opaqueBackground = opaqueBackgroundColor; } /** * Renders the border for the item renderer. * *

This method is called by drawBackground after the * background has been rendered.

* *

Override this method to change the appearance of the separator or * border of the item renderer.

* * @param unscaledWidth Specifies the width of the component, in pixels, * in the component's coordinates, regardless of the value of the * scaleX property of the component. * * @param unscaledHeight Specifies the height of the component, in pixels, * in the component's coordinates, regardless of the value of the * scaleY property of the component. * * @langversion 3.0 * @playerversion AIR 3.0 * @productversion Flex 4.6 */ protected function drawBorder(unscaledWidth:Number, unscaledHeight:Number):void { var topSeparatorColor:uint; var topSeparatorAlpha:Number; var bottomSeparatorColor:uint; var bottomSeparatorAlpha:Number; // separators are a highlight on the top and shadow on the bottom topSeparatorColor = 0xFFFFFF; topSeparatorAlpha = .3; bottomSeparatorColor = 0x000000; bottomSeparatorAlpha = .3; // draw separators // don't draw top separator for down and selected states if (!(selected || down)) { graphics.beginFill(topSeparatorColor, topSeparatorAlpha); graphics.drawRect(0, 0, unscaledWidth, 1); graphics.endFill(); } graphics.beginFill(bottomSeparatorColor, bottomSeparatorAlpha); graphics.drawRect(0, unscaledHeight - (isLastItem ? 0 : 1), unscaledWidth, 1); graphics.endFill(); // add extra separators to the first and last items so that // the list looks correct during the scrolling bounce/pull effect // top if (itemIndex == 0) { graphics.beginFill(bottomSeparatorColor, bottomSeparatorAlpha); graphics.drawRect(0, -1, unscaledWidth, 1); graphics.endFill(); } // bottom if (isLastItem) { // we want to offset the bottom by 1 so that we don't get // a double line at the bottom of the list if there's a // border graphics.beginFill(topSeparatorColor, topSeparatorAlpha); graphics.drawRect(0, unscaledHeight + 1, unscaledWidth, 1); graphics.endFill(); } } /** * Positions the children for this item renderer. * *

This method, along with drawBackground(), is called * by the updateDisplayList() method.

* *

This method positions the labelDisplay component. * Subclasses should override this to position their children.

* * @param unscaledWidth Specifies the width of the component, in pixels, * in the component's coordinates, regardless of the value of the * scaleX property of the component. * * @param unscaledHeight Specifies the height of the component, in pixels, * in the component's coordinates, regardless of the value of the * scaleY property of the component. * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ protected function layoutContents(unscaledWidth:Number, unscaledHeight:Number):void { if (!labelDisplay) return; var paddingLeft:Number = getStyle("paddingLeft"); var paddingRight:Number = getStyle("paddingRight"); var paddingTop:Number = getStyle("paddingTop"); var paddingBottom:Number = getStyle("paddingBottom"); var verticalAlign:String = getStyle("verticalAlign"); var viewWidth:Number = unscaledWidth - paddingLeft - paddingRight; var viewHeight:Number = unscaledHeight - paddingTop - paddingBottom; var vAlign:Number; if (verticalAlign == "top") vAlign = 0; else if (verticalAlign == "bottom") vAlign = 1; else // if (verticalAlign == "middle") vAlign = 0.5; // measure the label component // text should take up the rest of the space width-wise, but only let it take up // its measured textHeight so we can position it later based on verticalAlign var labelWidth:Number = Math.max(viewWidth, 0); var labelHeight:Number = 0; if (label != "") { labelDisplay.commitStyles(); // reset text if it was truncated before. if (labelDisplay.isTruncated) labelDisplay.text = label; labelHeight = getElementPreferredHeight(labelDisplay); } setElementSize(labelDisplay, labelWidth, labelHeight); // We want to center using the "real" ascent var labelY:Number = Math.round(vAlign * (viewHeight - labelHeight)) + paddingTop; setElementPosition(labelDisplay, paddingLeft, labelY); // attempt to truncate the text now that we have its official width labelDisplay.truncateToFit(); } //-------------------------------------------------------------------------- // // Methods: Layout Helpers // //-------------------------------------------------------------------------- /** * @copy spark.skins.mobile.supportClasses.MobileSkin#setElementPosition() * * @see #setElementSize * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ protected function setElementPosition(element:Object, x:Number, y:Number):void { if (element is ILayoutElement) { ILayoutElement(element).setLayoutBoundsPosition(x, y, false); } else if (element is IFlexDisplayObject) { IFlexDisplayObject(element).move(x, y); } else { element.x = x; element.y = y; } } /** * @copy spark.skins.mobile.supportClasses.MobileSkin#setElementSize() * * @see #setElementPosition * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ protected function setElementSize(element:Object, width:Number, height:Number):void { if (element is ILayoutElement) { ILayoutElement(element).setLayoutBoundsSize(width, height, false); } else if (element is IFlexDisplayObject) { IFlexDisplayObject(element).setActualSize(width, height); } else { element.width = width; element.height = height; } } /** * @copy spark.skins.mobile.supportClasses.MobileSkin#getElementPreferredWidth() * * @see #setElementPosition * @see #setElementSize * @see #getElementPreferredHeight * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ protected function getElementPreferredWidth(element:Object):Number { var result:Number; if (element is ILayoutElement) { result = ILayoutElement(element).getPreferredBoundsWidth(); } else if (element is IFlexDisplayObject) { result = IFlexDisplayObject(element).measuredWidth; } else { result = element.width; } return Math.round(result); } /** * @copy spark.skins.mobile.supportClasses.MobileSkin#getElementPreferredHeight() * * @see #setElementPosition * @see #setElementSize * @see #getElementPreferredWidth * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ protected function getElementPreferredHeight(element:Object):Number { var result:Number; if (element is ILayoutElement) { result = ILayoutElement(element).getPreferredBoundsHeight(); } else if (element is IFlexDisplayObject) { result = IFlexDisplayObject(element).measuredHeight; } else { result = element.height; } return Math.ceil(result); } //-------------------------------------------------------------------------- // // Event Handlers // //-------------------------------------------------------------------------- /** * @private */ private function interactionStateDetector_changeHandler(event:Event):void { down = (interactionStateDetector.state == InteractionState.DOWN); hovered = (interactionStateDetector.state == InteractionState.OVER); } } }