blob: 5133ae82cf789da27b0c591405b8563cc19fee14 [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.skins.ios7
{
import flash.events.Event;
import flash.events.FocusEvent;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.events.SoftKeyboardEvent;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.system.Capabilities;
import flash.text.TextLineMetrics;
import flash.ui.Keyboard;
import mx.core.DPIClassification;
import mx.core.EventPriority;
import mx.core.FlexGlobals;
import mx.core.mx_internal;
import mx.events.FlexEvent;
import mx.utils.Platform;
import spark.components.Group;
import spark.components.Scroller;
import spark.components.TextArea;
import spark.components.supportClasses.StyleableTextField;
import spark.events.CaretBoundsChangeEvent;
import spark.skins.ios7.supportClasses.TextSkinBase;
use namespace mx_internal;
/**
* ActionScript-based skin for TextArea components in mobile applications.
*
* @see spark.components.TextArea
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public class TextAreaSkin extends TextSkinBase
{
/**
* @private
* Right-margin of iOS native text control when editing on a retina display
* based on fontSize 32.
*/
mx_internal static var IOS_RIGHT_MARGIN_320:Number = 19;
/**
* @private
* Right-margin of iOS native text control when editing on a retina display
* based on fontSize 16 scaling from applicationDPI 160.
*/
mx_internal static var IOS_RIGHT_MARGIN_160_SCALED_TO_320:Number = 9.4;
/**
* @private
* Right-margin of iOS native text control when editing on a standard display
* based on fontSize 16 and runtimeDPI 160.
*/
mx_internal static var IOS_RIGHT_MARGIN_160:Number = 20.6;
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function TextAreaSkin()
{
super();
addEventListener(Event.RESIZE, resizeHandler);
switch (applicationDPI)
{
case DPIClassification.DPI_640:
{
measuredDefaultWidth = 1024;
measuredDefaultHeight = 212;
layoutBorderSize = 3;
roundheight = 24;
break;
}
case DPIClassification.DPI_480:
{
measuredDefaultWidth = 880;
measuredDefaultHeight = 140;
layoutBorderSize = 2;
roundheight = 18;
break;
}
case DPIClassification.DPI_320:
{
measuredDefaultWidth = 612;
measuredDefaultHeight = 106;
layoutBorderSize = 1.5;
roundheight = 14;
break;
}
case DPIClassification.DPI_240:
{
measuredDefaultWidth = 440;
measuredDefaultHeight = 70;
layoutBorderSize = 1;
roundheight = 10;
break;
}
case DPIClassification.DPI_120:
{
measuredDefaultWidth = 220;
measuredDefaultHeight = 35;
layoutBorderSize = .5;
roundheight = 5;
break;
}
default:
{
measuredDefaultWidth = 306;
measuredDefaultHeight = 53;
layoutBorderSize = .5;
roundheight = 7;
break;
}
}
addEventListener(FocusEvent.FOCUS_IN, focusChangeHandler);
addEventListener(FocusEvent.FOCUS_OUT, focusChangeHandler);
}
//--------------------------------------------------------------------------
//
// Skin parts
//
//--------------------------------------------------------------------------
/**
* Scroller skin part.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public var scroller:Scroller;
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
/**
* @copy spark.skins.spark.ApplicationSkin#hostComponent
*/
public var hostComponent:TextArea;
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
protected var isFocused:Boolean = false;
protected var roundheight:uint;
/**
* @private
* The width of the component on the previous layout manager
* pass. This gets set in updateDisplayList() and used in measure() on
* the next layout pass. This is so our "guessed width" in measure()
* will be as accurate as possible since textDisplay is multiline and
* the textDisplay height is dependent on the width.
*
* In the constructor this is actually set based on the DPI.
*/
mx_internal var oldUnscaledWidth:Number;
private var textDisplayGroup:Group;
private var _isIOS:Boolean;
private var invalidateCaretPosition:Boolean = true;
private var oldCaretBounds:Rectangle = new Rectangle(-1, -1, -1, -1);
private var lastTextHeight:Number;
private var lastTextWidth:Number;
private var isTextDisplayTall:Boolean = true;
private var growTextDisplay:Boolean = false;
private var shrinkTextDisplay:Boolean = false;
//--------------------------------------------------------------------------
//
// Overridden methods
//
//--------------------------------------------------------------------------
/**
* @private
*/
override protected function createChildren():void
{
if (!textDisplay)
{
// wrap StyleableTextField in UIComponent
textDisplay = StyleableTextField(createInFontContext(StyleableTextField));
textDisplay.styleName = this;
textDisplay.multiline = true;
textDisplay.editable = true;
textDisplay.lineBreak = getStyle("lineBreak");
textDisplay.useTightTextBounds = false;
textDisplay.scrollToRangeDelegate = scrollToRange;
// on iOS, resize the TextField and let the native control handle scrolling
_isIOS = Platform.isIOS;
if (_isIOS)
{
// hard-coded rightMargin for iOS native text control
// this value is independent of the paddingRight style
var rightMargin:Number = 0;
var isRetina:Boolean = false;
var isScaling160to320:Boolean = false;
// check for scaling
if ("runtimeDPI" in FlexGlobals.topLevelApplication)
{
var runtimeDPI:Number = FlexGlobals.topLevelApplication.runtimeDPI as Number;
isRetina = (runtimeDPI == DPIClassification.DPI_320);
isScaling160to320 = isRetina
&& (applicationDPI == DPIClassification.DPI_160);
}
if (isRetina && !isScaling160to320)
rightMargin = IOS_RIGHT_MARGIN_320;
else if (isRetina && isScaling160to320)
rightMargin = IOS_RIGHT_MARGIN_160_SCALED_TO_320;
else
rightMargin = IOS_RIGHT_MARGIN_160;
textDisplay.rightMargin = rightMargin;
}
else
{
textDisplay.addEventListener(KeyboardEvent.KEY_DOWN, textDisplay_keyHandler);
}
textDisplay.addEventListener(Event.CHANGE, textDisplay_changeHandler);
textDisplay.addEventListener(FlexEvent.VALUE_COMMIT, textDisplay_changeHandler);
textDisplay.addEventListener(Event.SCROLL, textDisplay_scrollHandler);
// Use a lower priority so that the StyleableTextField event handler is called first.
// That handler cancels the event and we need to check for that case
textDisplay.addEventListener(SoftKeyboardEvent.SOFT_KEYBOARD_ACTIVATING, textDisplay_softKeyboardActivatingHandler, false, EventPriority.DEFAULT_HANDLER);
textDisplay.addEventListener(SoftKeyboardEvent.SOFT_KEYBOARD_ACTIVATE, textDisplay_softKeyboardActivateHandler);
textDisplay.addEventListener(SoftKeyboardEvent.SOFT_KEYBOARD_DEACTIVATE, textDisplay_softKeyboardDeactivateHandler);
textDisplay.left = getStyle("paddingLeft");
textDisplay.top = getStyle("paddingTop");
textDisplay.right = getStyle("paddingRight");
textDisplay.bottom = getStyle("paddingBottom");
// wrap StyleableTextComponent in Group for viewport
textDisplayGroup = new Group();
textDisplayGroup.clipAndEnableScrolling = true;
textDisplayGroup.addElement(textDisplay);
}
if (!scroller)
{
scroller = new Scroller();
scroller.minViewportInset = 0;
scroller.measuredSizeIncludesScrollBars = false;
scroller.ensureElementIsVisibleForSoftKeyboard = false;
addChild(scroller);
}
if (!scroller.viewport)
scroller.viewport = textDisplayGroup;
super.createChildren();
}
/**
* @private
* TextArea prompt supports wrapping and multiline
*/
override protected function createPromptDisplay():StyleableTextField
{
var prompt:StyleableTextField = super.createPromptDisplay();
prompt.editable = true;
prompt.wordWrap = true;
return prompt;
}
/**
* @private
*/
override protected function measure():void
{
super.measure();
var paddingTop:Number = getStyle("paddingTop");
var paddingBottom:Number = getStyle("paddingBottom");
var paddingLeft:Number = getStyle("paddingLeft");
var paddingRight:Number = getStyle("paddingRight");
// TextDisplay always defaults to 440 pixels wide (the value is DPI dependent),
// and tall enough to show all text.
//
// You can set an explicit width and the height will adjust accordingly. The opposite
// is not true: setting an explicit height will not adjust the width accordingly.
measuredWidth = measuredDefaultWidth;
// now we need to measure textDisplay's height. Unfortunately, this is tricky and
// is dependent on textDisplay's width. Let's use the heuristic that our width
// is the same as our last width.
// We don't use layoutMeasuredWidth, because that value is just a constant and doesn't
// take into account the fact that the TextArea could have an explicitWidth or could
// be constrained by some value. However, we still default oldTextDisplayWidth to
// be layoutMeasuredWidth the first time through.
var textDisplayEstimatedWidth:Number = oldUnscaledWidth - paddingLeft - paddingRight;
// now we need to measure textDisplay's height. Unfortunately, this is tricky and
// is dependent on textDisplay's width.
// Use the old textDisplay width as an estimte for the new one.
// If we are wrong, we'll find out in updateDisplayList()
textDisplay.commitStyles();
// Clear min sizes first.
textDisplay.minWidth = textDisplay.minHeight = NaN;
// If lineBreak == explicit, always use NaN for estimated width
if (getStyle("lineBreak") == "explicit")
textDisplayEstimatedWidth = NaN;
setElementSize(textDisplay, textDisplayEstimatedWidth, NaN);
measuredHeight = getElementPreferredHeight(textDisplay) + paddingTop + paddingBottom;
}
/**
* @private
*/
override protected function layoutContents(unscaledWidth:Number, unscaledHeight:Number):void
{
super.layoutContents(unscaledWidth, unscaledHeight);
// position & size border
if (border)
{
setElementSize(border, unscaledWidth, unscaledHeight);
setElementPosition(border, 0, 0);
}
setElementSize(scroller, unscaledWidth, unscaledHeight);
setElementPosition(scroller, 0, 0);
// position & size the text
var explicitLineBreak:Boolean = getStyle("lineBreak") == "explicit";
var paddingLeft:Number = getStyle("paddingLeft");
var paddingRight:Number = getStyle("paddingRight");
var paddingTop:Number = getStyle("paddingTop");
var paddingBottom:Number = getStyle("paddingBottom");
var unscaledTextWidth:Number = unscaledWidth - paddingLeft - paddingRight;
var unscaledTextHeight:Number = unscaledHeight - paddingTop - paddingBottom;
var textHeight:Number;
var textWidth:Number = explicitLineBreak ? textDisplay.measuredTextSize.x : unscaledTextWidth;
var lineIndex:int;
var topCharIndex:int;
var charBounds:Rectangle;
// grab old measured textDisplay height before resizing it
var oldPreferredTextHeight:Number = getElementPreferredHeight(textDisplay);
// set width first to measure height correctly
textDisplay.commitStyles();
textDisplay.setLayoutBoundsSize(textWidth, NaN);
// In iOS, when we go into editing mode, the runtime overlays a native
// text control over the textDisplay. In order to prevent the text
// from overflowing the component and to get scrolling support, the
// native text control must be the same size as the TextArea
if (_isIOS)
{
if (shrinkTextDisplay)
{
// Switching to edit mode. Convert from viewport scrolling to
// TextField scrolling
var vsp:Number = textDisplayGroup.verticalScrollPosition;
var lineMetrics:TextLineMetrics = textDisplay.getLineMetrics(0);
var lineHeight:Number = lineMetrics.ascent + lineMetrics.descent;
// TODO Figure out how to get the x offset. Right now is hard coded to 2
// At least half the line should be showing before we scroll to that line
// This makes the conversion from pixel to line based scrolling a little less jumpy
lineIndex = textDisplay.getLineIndexAtPoint(2, vsp + lineHeight / 2) + 1;
textDisplayGroup.verticalScrollPosition = 0;
isTextDisplayTall = false;
//trace("TAS.layoutContents shrinkText vsp",vsp,"lineIndex",lineIndex);
}
else if (growTextDisplay)
{
// Leaving edit mode. Convert from TextField scrolling to
// viewport scrolling
var scrollV:Number = textDisplay.scrollV;
// TODO (jszeto) investigate using lineMetrics.lineHeight * scrollV instead of getCharBoundaries
topCharIndex = textDisplay.getLineOffset(scrollV - 1);
charBounds = textDisplay.getCharBoundaries(topCharIndex);
// If the charBounds is null, just set vsp to 0
if (charBounds == null)
charBounds = new Rectangle(0, 0, 0, 0);
textDisplay.scrollV = 1;
isTextDisplayTall = true;
//trace("TAS.layoutContents growText scrollV",scrollV,"topCharIndex",topCharIndex,"charBounds",charBounds);
}
}
// TextField height should match its content or the TextArea bounds at minimum
// iOS special case to prevent Flex Scroller scrolling when editable
if (isTextDisplayTall)
textHeight = Math.max(textDisplay.measuredTextSize.y, unscaledTextHeight);
else
textHeight = unscaledTextHeight;
// FIXME (jasonsj): iOS native scroll bar appears even when explictHeight
// is not specified. Focus-in is jumpy.
if (promptDisplay)
{
promptDisplay.commitStyles();
setElementSize(promptDisplay, unscaledTextWidth, textHeight);
setElementPosition(promptDisplay, paddingLeft, paddingTop);
// no need to update textDisplay if promptDisplay is present
return;
}
// keep track of oldUnscaledWidth so we have a good guess as to the width
// of the textDisplay on the next measure() pass
oldUnscaledWidth = unscaledWidth;
// set the width of textDisplay to textWidth.
// set the height to oldTextHeight. If the height's actually wrong,
// we'll invalidateSize() and go through this layout pass again anyways
setElementSize(textDisplay, textWidth, textHeight);
// Set minWidth/Height on the text so the textDisplayGroup sizes accordingly
textDisplay.minWidth = textWidth;
textDisplay.minHeight = textHeight;
textDisplayGroup.invalidateDisplayList();
// grab new measured textDisplay height after the textDisplay has taken its final width
var newPreferredTextHeight:Number = getElementPreferredHeight(textDisplay);
// if the resize caused the textDisplay's height to change (because of
// text reflow), then we need to remeasure ourselves with our new width
if (oldPreferredTextHeight != newPreferredTextHeight)
invalidateSize();
if (_isIOS)
{
if (shrinkTextDisplay)
{
scroller.validateNow();
textDisplay.scrollV = lineIndex;
}
else if (growTextDisplay)
{
scroller.validateNow();
textDisplayGroup.verticalScrollPosition = charBounds.y;
}
shrinkTextDisplay = false;
growTextDisplay = false;
}
//trace("TAS.layoutContents tH",textHeight,"tW",textWidth,"invalidateCaret",invalidateCaretPosition);
// checking if text fits in TextArea
// does not apply to iOS due to native text editing and scrolling
// invalidateCaretPosition will never be true for iOS
if (invalidateCaretPosition && isTextDisplayTall)
{
// if the caret is outside the viewport, update the Group verticalScrollPosition
var charIndex:int = textDisplay.selectionBeginIndex;
var caretBounds:Rectangle = textDisplay.getCharBoundaries(charIndex);
lineIndex = textDisplay.getLineIndexOfChar(charIndex);
// getCharBoundaries() returns null for new lines
if (!caretBounds)
{
// temporarily insert a character at the caretIndex
textDisplay.replaceText(charIndex, charIndex, "W");
caretBounds = textDisplay.getCharBoundaries(charIndex);
lineIndex = textDisplay.getLineIndexOfChar(charIndex);
textDisplay.replaceText(charIndex, charIndex + 1, "");
}
if (caretBounds)
{
// Scroll the internal Scroller to ensure the caret is visible
if (textHeight > unscaledTextHeight)
{
if (charIndex == textDisplay.text.length)
{
// Make sure textDisplayGroup is validated, otherwise the
// verticalScrollPosition may be out of bounds, which will
// cause a bounce effect.
textDisplayGroup.validateNow();
textDisplayGroup.verticalScrollPosition = textHeight;
}
else
{
// caretTopPositon and caretBottomPosition are TextField-relative positions
// the TextField is inset by padding styles of the TextArea (via the VGroup)
// adjust top position to 0 when on the first line
// caretTopPosition will be negative when off stage
var caretTopPosition:Number = ((caretBounds.y) < 0 || (lineIndex == 0))
? 0 : caretBounds.y;
// caretBottomPosition is the y coordinate of the bottom bounds of the caret
var caretBottomPosition:Number = caretBounds.y + caretBounds.height;
// note that verticalScrollPosition min/max do not account for padding
var vspTop:Number = textDisplayGroup.verticalScrollPosition;
// vspBottom should be the max visible Y in the TextField
// coordinate space.
// remove paddingBottom for some clearance between caret and border
var vspBottom:Number = vspTop + unscaledHeight - paddingTop - paddingBottom;
// is the caret in or below the padding and viewport?
if (caretBottomPosition > vspBottom)
{
// adjust caretBottomPosition to max scroll position when on the last line
if (lineIndex + 1 == textDisplay.numLines)
{
// use textHeight+paddings instead of textDisplayGroup.contentHeight
// Group has not been resized by this point
textDisplayGroup.verticalScrollPosition = (textHeight + paddingTop + paddingBottom) - textDisplayGroup.height;
}
else
{
// bottom edge of the caret moves just inside the bottom edge of the scroller
// add delta between caret and vspBottom
textDisplayGroup.verticalScrollPosition = vspTop + (caretBottomPosition - vspBottom);
}
}
// is the caret above the viewport?
else if (caretTopPosition < vspTop)
{
// top edge of the caret moves inside the top edge of the scroller
textDisplayGroup.verticalScrollPosition = caretTopPosition;
}
}
scroller.validateNow();
}
// Convert to local coordinates
// Dispatch an event for an ancestor Scroller
// It will scroll the TextArea so the caret is in view
convertBoundsToLocal(caretBounds);
if (oldCaretBounds == null || caretBounds.bottom != oldCaretBounds.bottom || caretBounds.top != oldCaretBounds.top)
{
//trace("TAS.layoutContents send caret CHANGE");
dispatchEvent(new CaretBoundsChangeEvent(CaretBoundsChangeEvent.CARET_BOUNDS_CHANGE,true,true,oldCaretBounds,caretBounds));
}
oldCaretBounds = caretBounds;
}
invalidateCaretPosition = false;
}
// Make sure final scroll position is valid
if (isTextDisplayTall)
snapTextScrollPosition();
}
override protected function drawBackground(unscaledWidth:Number, unscaledHeight:Number):void
{
super.drawBackground(unscaledWidth, unscaledHeight);
var contentBackgroundColor:uint = getStyle("contentBackgroundColor");
var contentBackgroundAlpha:Number = getStyle("contentBackgroundAlpha");
//change border color and thickness when in focus
var borderColor:uint = isFocused ? getStyle("focusColor") : getStyle("borderColor");
var borderWidth:uint = layoutBorderSize * 2;
if (isNaN(contentBackgroundAlpha))
{
contentBackgroundAlpha = 1;
}
if (getStyle("contentBackgroundBorder") == "roundedrect")
{
graphics.lineStyle(layoutBorderSize, borderColor, 1, true);
graphics.beginFill(contentBackgroundColor, contentBackgroundAlpha);
graphics.drawRoundRectComplex(layoutBorderSize, layoutBorderSize, unscaledWidth - borderWidth, unscaledHeight - borderWidth, roundheight, roundheight, roundheight, roundheight);
graphics.endFill();
}
if (getStyle("contentBackgroundBorder") == "rectangle")
{
//rectangle border and background
graphics.lineStyle(layoutBorderSize, borderColor, 1);
graphics.beginFill(contentBackgroundColor, contentBackgroundAlpha);
graphics.drawRect(layoutBorderSize, layoutBorderSize, unscaledWidth - borderWidth, unscaledHeight - borderWidth);
graphics.endFill();
}
else if (getStyle("contentBackgroundBorder") == "none")
{
//rectangle border and background
graphics.beginFill(contentBackgroundColor, contentBackgroundAlpha);
graphics.drawRect(0, 0, unscaledWidth - borderWidth, unscaledHeight - borderWidth);
graphics.endFill();
}
}
/**
* @private
* Make sure the scroll positions are valid, and adjust if needed.
*/
private function snapTextScrollPosition():void
{
var maxHsp:Number = textDisplayGroup.contentWidth > textDisplayGroup.width ?
textDisplayGroup.contentWidth-textDisplayGroup.width : 0;
textDisplayGroup.horizontalScrollPosition = Math.min(Math.max(0,textDisplayGroup.horizontalScrollPosition),maxHsp);
var maxVsp:Number = textDisplayGroup.contentHeight > textDisplayGroup.height ?
textDisplayGroup.contentHeight-textDisplayGroup.height : 0;
textDisplayGroup.verticalScrollPosition =
Math.min(Math.max(0,textDisplayGroup.verticalScrollPosition),maxVsp);
}
/**
* @private
* Get the bounds of the caret
*/
private function getCaretBounds():Rectangle
{
var charIndex:int = textDisplay.selectionBeginIndex;
var caretBounds:Rectangle = textDisplay.getCharBoundaries(charIndex);
if (!caretBounds)
{
textDisplay.replaceText(charIndex, charIndex, "W");
caretBounds = textDisplay.getCharBoundaries(charIndex);
textDisplay.replaceText(charIndex, charIndex + 1, "");
}
return caretBounds;
}
/**
* @private
* Convert bounds from textDisplay to local coordinates
*/
private function convertBoundsToLocal(bounds:Rectangle):void
{
if (bounds)
{
var position:Point = new Point(bounds.x, bounds.y);
position = textDisplay.localToGlobal(position);
position = globalToLocal(position);
bounds.x = position.x;
bounds.y = position.y;
}
}
/**
* @private
*/
private function scrollToRange(anchorPosition:int, activePosition:int):void
{
var pos:int = Math.min(anchorPosition, activePosition);
var bounds:Rectangle = textDisplay.getCharBoundaries(pos);
var vsp:int = textDisplayGroup.verticalScrollPosition;
var paddingTop:Number = getStyle("paddingTop");
var paddingBottom:Number = getStyle("paddingBottom");
if (bounds && (bounds.top < vsp - paddingTop ||
bounds.bottom > vsp + unscaledHeight - paddingTop - paddingBottom))
{
textDisplayGroup.verticalScrollPosition = bounds.top + paddingTop;
snapTextScrollPosition();
}
}
/**
* @private
* Handle size and caret position changes that occur when text content
* changes.
*/
private function textDisplay_changeHandler(event:Event):void
{
var tH:Number = textDisplay.textHeight;
var tW:Number = textDisplay.textWidth;
var explicitLineBreak:Boolean = getStyle("lineBreak") == "explicit";
// Size and caret position have changed if the text height is different or
// the text width is different and we aren't word wrapping
if (tH != lastTextHeight || ( explicitLineBreak && tW != lastTextWidth))
{
invalidateSize();
invalidateDisplayList();
invalidateCaretPosition = true;
}
lastTextHeight = tH;
lastTextWidth = tW;
}
/**
* @private
* Cancels any native scroll that the Flash Player attempts to do
*/
private function textDisplay_scrollHandler(event:Event):void
{
// if iOS, let the OS handle scrolling
if (_isIOS)
return;
// If not IOS, we will handle scrolling, so don't let the native
// flash textfield scroll at all.
if (textDisplay.scrollV > 1)
textDisplay.scrollV = 1;
if (textDisplay.scrollH > 0)
textDisplay.scrollH = 0;
}
/**
* @private
* Adjust viewport when using key navigation
*/
private function textDisplay_keyHandler(event:KeyboardEvent):void
{
// update scroll position when caret changes
if ((event.keyCode == Keyboard.UP
|| event.keyCode == Keyboard.DOWN
|| event.keyCode == Keyboard.LEFT
|| event.keyCode == Keyboard.RIGHT))
{
invalidateDisplayList();
invalidateCaretPosition = true;
}
// Change event is not always sent when delete key is pressed, so
// invalidate the size here
if (event.keyCode == Keyboard.BACKSPACE)
{
invalidateSize();
}
}
/**
* @private
* When entering edit mode on iOS, we need to shrink the textDisplay to
* the size of the TextArea
*/
private function textDisplay_softKeyboardActivatingHandler(event:SoftKeyboardEvent):void
{
if (event.isDefaultPrevented())
return;
if (_isIOS && isTextDisplayTall)
{
//trace("TAS.SK ACTIVATING targ",event.target);
shrinkTextDisplay = true;
invalidateDisplayList();
validateNow();
}
}
/**
* @private
* Send a caret change event to an ancestor Scroller
*/
private function textDisplay_softKeyboardActivateHandler(event:SoftKeyboardEvent):void
{
var keyboardRect:Rectangle = stage.softKeyboardRect;
if (keyboardRect.width > 0 && keyboardRect.height > 0)
{
var newCaretBounds:Rectangle = getCaretBounds();
convertBoundsToLocal(newCaretBounds);
if (oldCaretBounds != newCaretBounds)
{
//trace("TAS.SK ACTIVATE",keyboardRect,"dispatch caret CHANGE","newCaretBounds",newCaretBounds);
dispatchEvent(new CaretBoundsChangeEvent(CaretBoundsChangeEvent.CARET_BOUNDS_CHANGE,true,true,oldCaretBounds,newCaretBounds));
oldCaretBounds = newCaretBounds;
}
}
}
/**
* @private
* On iOS, when leaving edit mode, we need to restore the textDisplay to the
* height of the text.
*/
private function textDisplay_softKeyboardDeactivateHandler(event:SoftKeyboardEvent):void
{
if (_isIOS && !isTextDisplayTall)
{
growTextDisplay = true;
invalidateDisplayList();
}
}
/**
* @private
*/
private function resizeHandler(event:Event):void
{
// Resizing needs to tickle the TextArea's internal auto-scroll logic
invalidateCaretPosition = true;
invalidateDisplayList();
}
/**
* @private
*/
override public function styleChanged(styleProp:String):void
{
super.styleChanged(styleProp);
// propogate styleChanged explicitly to textDisplay
if (textDisplay)
textDisplay.styleChanged(styleProp);
// Check for padding style changes
if (!styleProp || styleProp == "styleName" || styleProp.indexOf("padding") >= 0)
{
if (textDisplay)
{
textDisplay.left = getStyle("paddingLeft");
textDisplay.top = getStyle("paddingTop");
textDisplay.right = getStyle("paddingRight");
textDisplay.bottom = getStyle("paddingBottom");
}
}
}
private function focusChangeHandler(event:FocusEvent):void
{
isFocused = event.type == FocusEvent.FOCUS_IN;
invalidateDisplayList();
}
}
}