blob: 42337aeb15d9e831dc9971237d6f50d841488c7c [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
//
// 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.supportClasses
{
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.Point;
import mx.core.InteractionMode;
import mx.core.mx_internal;
import mx.events.FlexEvent;
import spark.components.DataRenderer;
import spark.components.IItemRenderer;
import spark.components.ResizeMode;
use namespace mx_internal;
/**
* The ItemRenderer class is the base class for Spark item renderers.
*
* <p>Item renderers support optional view states.
* You typically use view states in MXML item renderers to control
* the appearance of a data item based on user interaction with the item.
* The ItemRenderer class supports all views states so that you can use
* those item renderers with list-based classes.</p>
*
* <p>Flex defines the following view states that you can support
* in your item renderers: </p>
* <ul>
* <li>normal - The data item has no user interaction.</li>
* <li>hovered - The mouse is over the data item.</li>
* <li>selected - The data item is selected.</li>
* <li>dragging - The data item is being dragged.</li>
* <li>normalAndShowCaret - The data item is in the normal state,
* and it has focus in the item list. </li>
* <li>hoveredAndShowCaret - The data item is in the hovered state,
* and it has focus in the item list.</li>
* <li>selectedAndShowCaret - The data item is in the normal state,
* and it has focus in the item list.</li>
* </ul>
*
* <p>When the user interacts with a control in a way that changes
* the view state of the item renderer, Flex first determines if the
* renderer defines that view state.
* If the item renderer supports the view state, Flex sets the item renderer
* to use that view state.
* If the item renderer does not supports the view state, Flex does nothing.</p>
*
* @mxml
*
* <p>The <code>&lt;s:ItemRenderer&gt;</code> tag inherits all of the tag
* attributes of its superclass and adds the following tag attributes:</p>
*
* <pre>
* &lt;s:ItemRenderer
* <strong>Properties</strong>
* autoDrawBackground="true"
* selected="false"
* showsCaret="false"
* /&gt;
* </pre>
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public class ItemRenderer extends DataRenderer implements IItemRenderer
{
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function ItemRenderer()
{
super();
// Initially state is dirty
rendererStateIsDirty = true;
interactionStateDetector = new InteractionStateDetector(this);
interactionStateDetector.addEventListener(Event.CHANGE, interactionStateDetector_changeHandler);
}
//--------------------------------------------------------------------------
//
// Private Properties
//
//--------------------------------------------------------------------------
/**
* @private
* Helper class to help determine when we are in the hovered or down states
*/
private var interactionStateDetector:InteractionStateDetector;
/**
* @private
* Whether the renderer's state is invalid or not.
*/
private var rendererStateIsDirty:Boolean = false;
/**
* @private
* A flag associated with rendererStateIsDirty, determining if
* this renderer should play any associated transitions
* in the next validation pass. This is different from the mx_internal
* playTransitions flag, which is set externally by List and DataGroup.
*/
private var playTransitionsOnNextRendererState:Boolean = false;
/**
* @private
* A flag determining if this renderer should play any
* associated transitions when a state change occurs.
*/
mx_internal var playTransitions:Boolean = true;
//--------------------------------------------------------------------------
//
// Public Properties
//
//--------------------------------------------------------------------------
//----------------------------------
// autoDrawBackground
//----------------------------------
/**
* @private
* storage for the autoDrawBackground property
*/
private var _autoDrawBackground:Boolean = true;
/**
* Specifies whether the item renderer draws the
* background of the data item during user interaction.
* Interactions include moving the mouse over the item,
* selecting the item, and moving the caret of the item.
*
* <p>If <code>true</code>, the background for
* the item renderer is automatically drawn, and it
* depends on the styles that are set (<code>contentBackgroundColor</code>,
* <code>alternatingItemColor</code>, <code>rollOverColor</code>,
* <code>downColor</code>, <code>selectionColor</code>)
* and the state that the item renderer is in.</p>
*
* <p>If <code>false</code>, the item render draws no backgrounds.
* Your custom item renderer is responsible for displaying the
* background colors for all user interactions.</p>
*
* @default true
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function get autoDrawBackground():Boolean
{
return _autoDrawBackground;
}
/**
* @private
*/
public function set autoDrawBackground(value:Boolean):void
{
if (_autoDrawBackground == value)
return;
_autoDrawBackground = value;
if (_autoDrawBackground)
{
redrawRequested = true;
super.$invalidateDisplayList();
}
}
//----------------------------------
// down
//----------------------------------
/**
* @private
* storage for the down property
*/
private var _down:Boolean = false;
/**
* Set to <code>true</code> when the user is pressing down on an item renderer.
*
* @default false
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
protected function get down():Boolean
{
return _down;
}
/**
* @private
*/
protected function set down(value:Boolean):void
{
if (value != _down)
{
_down = value;
invalidateRendererState();
if (autoDrawBackground)
{
redrawRequested = true;
super.$invalidateDisplayList();
}
}
}
//----------------------------------
// hovered
//----------------------------------
/**
* @private
* storage for the hovered property
*/
private var _hovered:Boolean = false;
/**
* Set to <code>true</code> when the user is hovered over the item renderer.
*
* @default false
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
protected function get hovered():Boolean
{
return _hovered;
}
/**
* @private
*/
protected function set hovered(value:Boolean):void
{
if (value != _hovered)
{
_hovered = value;
invalidateRendererState();
if (autoDrawBackground)
{
redrawRequested = true;
super.$invalidateDisplayList();
}
}
}
//----------------------------------
// itemIndex
//----------------------------------
/**
* @private
* storage for the itemIndex property
*/
private var _itemIndex:int;
[Bindable("itemIndexChanged")]
/**
* @inheritDoc
*
* @default 0
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function get itemIndex():int
{
return _itemIndex;
}
/**
* @private
*/
public function set itemIndex(value:int):void
{
if (value == _itemIndex)
return;
_itemIndex = value;
if (autoDrawBackground)
{
redrawRequested = true;
super.$invalidateDisplayList();
}
dispatchEvent(new Event("itemIndexChanged"));
}
//----------------------------------
// labelDisplay
//----------------------------------
/**
* Optional item renderer label component.
* This component is used to determine the value of the
* <code>baselinePosition</code> property in the host component of
* the item renderer.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
[Bindable]
public var labelDisplay:TextBase;
//----------------------------------
// showsCaret
//----------------------------------
/**
* @private
* Storage for the showsCaret property
*/
private var _showsCaret:Boolean = false;
/**
* @inheritDoc
*
* @default false
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function get showsCaret():Boolean
{
return _showsCaret;
}
/**
* @private
*/
public function set showsCaret(value:Boolean):void
{
if (value == _showsCaret)
return;
_showsCaret = value;
invalidateRendererState();
if (autoDrawBackground)
{
redrawRequested = true;
super.$invalidateDisplayList();
}
}
//----------------------------------
// selected
//----------------------------------
/**
* @private
* storage for the selected property
*/
private var _selected:Boolean = false;
/**
* @inheritDoc
*
* @default false
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function get selected():Boolean
{
return _selected;
}
/**
* @private
*/
public function set selected(value:Boolean):void
{
if (value != _selected)
{
_selected = value;
invalidateRendererState();
if (autoDrawBackground)
{
redrawRequested = true;
super.$invalidateDisplayList();
}
}
}
//----------------------------------
// dragging
//----------------------------------
/**
* @private
* Storage for the dragging property.
*/
private var _dragging:Boolean = false;
/**
* @inheritDoc
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function get dragging():Boolean
{
return _dragging;
}
/**
* @private
*/
public function set dragging(value:Boolean):void
{
if (value != _dragging)
{
_dragging = value;
invalidateRendererState();
}
}
//----------------------------------
// label
//----------------------------------
/**
* @private
* Storage var for label
*/
private var _label:String = "";
[Bindable("labelChanged")]
/**
* @inheritDoc
*
* @default ""
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
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 labelDisplay,
// if it exists
if (labelDisplay)
labelDisplay.text = _label;
dispatchEvent(new Event("labelChanged"));
}
//--------------------------------------------------------------------------
//
// Overridden Properties - UIComponent
//
//--------------------------------------------------------------------------
//----------------------------------
// baselinePosition
//----------------------------------
/**
* @private
*/
override public function get baselinePosition():Number
{
if (!validateBaselinePosition() || !labelDisplay)
return super.baselinePosition;
var labelPosition:Point = globalToLocal(labelDisplay.parent.localToGlobal(
new Point(labelDisplay.x, labelDisplay.y)));
return labelPosition.y + labelDisplay.baselinePosition;
}
//--------------------------------------------------------------------------
//
// Methods - ItemRenderer State Support
//
//--------------------------------------------------------------------------
/**
* Returns the name of the state to be applied to the renderer. For example, a
* very basic List item renderer would return the String "normal", "hovered",
* or "selected" to specify the renderer's state.
* If dealing with touch interactions (or mouse interactions where selection
* is ignored), "down" and "downAndSelected" are also important states.
*
* <p>A subclass of ItemRenderer must override this method to return a value
* if the behavior desired differs from the default behavior.</p>
*
* <p>In Flex 4.0, the 3 main states were "normal", "hovered", and "selected".
* In Flex 4.5, "down" and "downAndSelected" have been added.</p>
*
* <p>The full set of states supported (in order of precedence) are:
* <ul>
* <li>dragging</li>
* <li>downAndSelected</li>
* <li>selectedAndShowsCaret</li>
* <li>hoveredAndShowsCaret</li>
* <li>normalAndShowsCaret</li>
* <li>down</li>
* <li>selected</li>
* <li>hovered</li>
* <li>normal</li>
* </ul>
* </p>
*
* @return A String specifying the name of the state to apply to the renderer.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
protected function getCurrentRendererState():String
{
// this code is pretty confusing without multi-dimensional states, but it's
// defined in order of precedence.
if (dragging && hasState("dragging"))
return "dragging";
if (selected && down && hasState("downAndSelected"))
return "downAndSelected";
if (selected && showsCaret && hasState("selectedAndShowsCaret"))
return "selectedAndShowsCaret";
if (hovered && showsCaret && hasState("hoveredAndShowsCaret"))
return "hoveredAndShowsCaret";
if (showsCaret && hasState("normalAndShowsCaret"))
return "normalAndShowsCaret";
if (down && hasState("down"))
return "down";
if (selected && hasState("selected"))
return "selected";
if (hovered && hasState("hovered"))
return "hovered";
if (hasState("normal"))
return "normal";
// If none of the above states are defined in the item renderer,
// we return currentState, so we don't change the state just
// in case the developer put the item renderer into its
// own custom state.
return currentState;
}
/**
* Marks the renderer's state as invalid so that the new state is set
* during a later screen update.
*
* @langversion 3.0
* @playerversion Flash 10.1
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
protected function invalidateRendererState():void
{
playTransitionsOnNextRendererState = (playTransitionsOnNextRendererState || playTransitions);
if (rendererStateIsDirty)
return; // State is already invalidated
rendererStateIsDirty = true;
invalidateProperties();
}
//--------------------------------------------------------------------------
//
// Overridden Methods
//
//--------------------------------------------------------------------------
/**
* @private
*/
override protected function commitProperties():void
{
// need to run this code before calling super.commitProperites
// because the super.commitProperties can handle state change on
// rare occassion (during initialization)
if (rendererStateIsDirty)
{
setCurrentState(getCurrentRendererState(), playTransitionsOnNextRendererState);
playTransitionsOnNextRendererState = false;
rendererStateIsDirty = false;
}
super.commitProperties();
}
/**
* @private
*/
override public function styleChanged(styleName:String):void
{
var allStyles:Boolean = styleName == null || styleName == "styleName";
super.styleChanged(styleName);
if (autoDrawBackground && (allStyles || styleName == "alternatingItemColors" ||
styleName == "contentBackgroundColor" || styleName == "rollOverColor" ||
styleName == "downColor" || styleName == "selectionColor"))
{
redrawRequested = true;
super.$invalidateDisplayList();
}
}
/**
* @private
*/
override mx_internal function drawBackground():void
{
// if autoDrawBackground is set to true, we always
// draw a background and don't need to worry about mouseEnabledWhereTransparent.
// However, if it's false, then we should just let super.drawBackground()
// do its job.
if (!autoDrawBackground)
{
super.drawBackground();
return;
}
// TODO (rfrishbe): Would be good to remove this duplicate code with the
// super.drawBackground() version
var w:Number = (resizeMode == ResizeMode.SCALE) ? measuredWidth : unscaledWidth;
var h:Number = (resizeMode == ResizeMode.SCALE) ? measuredHeight : unscaledHeight;
if (isNaN(w) || isNaN(h))
return;
graphics.clear();
var backgroundColor:uint;
var drawBackground:Boolean = true;
var downColor:* = getStyle("downColor");
if (down && downColor !== undefined)
backgroundColor = downColor;
else if (selected)
backgroundColor = getStyle("selectionColor");
else if (hovered)
backgroundColor = getStyle("rollOverColor");
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;
}
}
graphics.beginFill(backgroundColor, drawBackground ? 1 : 0);
if (showsCaret)
{
graphics.lineStyle(1, getStyle("selectionColor"));
graphics.drawRect(0.5, 0.5, w-1, h-1);
}
else
{
graphics.lineStyle();
graphics.drawRect(0, 0, w, h);
}
graphics.endFill();
}
//--------------------------------------------------------------------------
//
// Event handling
//
//--------------------------------------------------------------------------
/**
* @private
*/
private function interactionStateDetector_changeHandler(event:Event):void
{
playTransitions = interactionStateDetector.playTransitions;
down = (interactionStateDetector.state == InteractionState.DOWN);
hovered = (interactionStateDetector.state == InteractionState.OVER);
playTransitions = true;
}
}
}