Return-Path: X-Original-To: apmail-flex-commits-archive@www.apache.org Delivered-To: apmail-flex-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 5687311D23 for ; Sun, 17 Aug 2014 08:24:02 +0000 (UTC) Received: (qmail 71236 invoked by uid 500); 17 Aug 2014 08:24:01 -0000 Delivered-To: apmail-flex-commits-archive@flex.apache.org Received: (qmail 71134 invoked by uid 500); 17 Aug 2014 08:24:01 -0000 Mailing-List: contact commits-help@flex.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@flex.apache.org Delivered-To: mailing list commits@flex.apache.org Received: (qmail 70147 invoked by uid 99); 17 Aug 2014 08:24:01 -0000 Received: from tyr.zones.apache.org (HELO tyr.zones.apache.org) (140.211.11.114) by apache.org (qpsmtpd/0.29) with ESMTP; Sun, 17 Aug 2014 08:24:01 +0000 Received: by tyr.zones.apache.org (Postfix, from userid 65534) id 14A829298C5; Sun, 17 Aug 2014 08:24:01 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: bigosmallm@apache.org To: commits@flex.apache.org Date: Sun, 17 Aug 2014 08:24:25 -0000 Message-Id: <9ac265acdfbe41e1869de268ad3d0b01@git.apache.org> In-Reply-To: <254b74584ca1481595866e19202dd3f9@git.apache.org> References: <254b74584ca1481595866e19202dd3f9@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [26/47] git commit: [flex-sdk] [refs/heads/develop] - StageTextInput and StageTextArea skins. Contributed by Aaron Nash. Details here: https://issues.apache.org/jira/browse/FLEX-34395 StageTextInput and StageTextArea skins. Contributed by Aaron Nash. Details here: https://issues.apache.org/jira/browse/FLEX-34395 Project: http://git-wip-us.apache.org/repos/asf/flex-sdk/repo Commit: http://git-wip-us.apache.org/repos/asf/flex-sdk/commit/75236aa8 Tree: http://git-wip-us.apache.org/repos/asf/flex-sdk/tree/75236aa8 Diff: http://git-wip-us.apache.org/repos/asf/flex-sdk/diff/75236aa8 Branch: refs/heads/develop Commit: 75236aa867f3962452d1a4497923b5b653372cb5 Parents: 4eec881 Author: Om Authored: Wed Jul 2 16:52:14 2014 -0700 Committer: Om Committed: Wed Jul 2 16:52:14 2014 -0700 ---------------------------------------------------------------------- .../projects/mobiletheme/src/android4x.css | 28 + .../spark/skins/android4/StageTextAreaSkin.as | 190 ++++ .../spark/skins/android4/StageTextInputSkin.as | 134 +++ .../src/spark/skins/android4/TextAreaSkin.as | 892 +++++++++++++++++++ .../src/spark/skins/android4/TextInputSkin.as | 376 ++++++++ .../src/spark/skins/android4/TextSkinBase.as | 212 +++++ .../supportClasses/StageTextSkinBase.as | 402 +++++++++ 7 files changed, 2234 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/flex-sdk/blob/75236aa8/frameworks/projects/mobiletheme/src/android4x.css ---------------------------------------------------------------------- diff --git a/frameworks/projects/mobiletheme/src/android4x.css b/frameworks/projects/mobiletheme/src/android4x.css index 956a78c..02511c1 100644 --- a/frameworks/projects/mobiletheme/src/android4x.css +++ b/frameworks/projects/mobiletheme/src/android4x.css @@ -192,6 +192,34 @@ TabbedViewNavigator #tabBar textShadowColor: #000000; } +TextInput +{ + skinClass: ClassReference("spark.skins.android4.StageTextInputSkin"); + contentBackgroundAlpha: 0; + focusColor: #33B5E5; + borderColor: #4e4e4e; + contentBackgroundColor: #DEDEDD; + contentBackgroundBorder: "flat"; + fontFamily: RobotoRegular; + selectionHighlighting: "never"; + focusEnabled:"false"; + focusThickness: 0; +} + +TextArea +{ + skinClass: ClassReference("spark.skins.android4.StageTextAreaSkin"); + contentBackgroundAlpha: 1; + contentBackgroundBorder: "rectangle"; + contentBackgroundColor: #DEDEDD; + focusColor: #33B5E5; + fontFamily: RobotoRegular; + borderColor: #4e4e4e; + selectionHighlighting: "never"; + focusEnabled:"false"; + focusThickness: 0; +} + ToggleSwitch { accentColor: #3F7FBA; http://git-wip-us.apache.org/repos/asf/flex-sdk/blob/75236aa8/frameworks/projects/mobiletheme/src/spark/skins/android4/StageTextAreaSkin.as ---------------------------------------------------------------------- diff --git a/frameworks/projects/mobiletheme/src/spark/skins/android4/StageTextAreaSkin.as b/frameworks/projects/mobiletheme/src/spark/skins/android4/StageTextAreaSkin.as new file mode 100644 index 0000000..d48377d --- /dev/null +++ b/frameworks/projects/mobiletheme/src/spark/skins/android4/StageTextAreaSkin.as @@ -0,0 +1,190 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// 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.android4 +{ + + import mx.core.DPIClassification; + import mx.core.mx_internal; + + import spark.components.TextArea; + import spark.components.supportClasses.IStyleableEditableText; + import spark.components.supportClasses.ScrollableStageText; + import spark.components.supportClasses.StyleableTextField; + import spark.skins.android4.supportClasses.StageTextSkinBase; + + use namespace mx_internal; + + /** + * ActionScript-based skin for TextArea controls in mobile applications that uses a + * StyleableStageText class for the text display. + * + * @see spark.components.TextArea + * @see spark.components.supportClasses.StyleableStageText + * + * @langversion 3.0 + * @playerversion AIR 3.0 + * @productversion Flex 4.6 + */ + public class StageTextAreaSkin extends StageTextSkinBase + { + //-------------------------------------------------------------------------- + // + // Class variables + // + //-------------------------------------------------------------------------- + + /** + * The underlying native text control on iOS has internal margins of its + * own. In order to remain faithful to the paddingTop and paddingBottom + * style values that developers may specify, those internal margins need to + * be compensated for. This variable contains size of that compensation in + * pixels. + */ + mx_internal static var iOSVerticalPaddingAdjustment:Number = 5; + + //-------------------------------------------------------------------------- + // + // Constructor + // + //-------------------------------------------------------------------------- + + /** + * Constructor. + * + * @langversion 3.0 + * @playerversion AIR 3.0 + * @productversion Flex 4.6 + */ + public function StageTextAreaSkin() + { + super(); + multiline = true; + + switch (applicationDPI) + { + case DPIClassification.DPI_640: + { + measuredDefaultHeight = 212; + break; + } + case DPIClassification.DPI_480: + { + measuredDefaultHeight = 140; + break; + } + case DPIClassification.DPI_320: + { + measuredDefaultHeight = 106; + break; + } + case DPIClassification.DPI_240: + { + measuredDefaultHeight = 70; + break; + } + case DPIClassification.DPI_120: + { + measuredDefaultHeight = 35; + break; + } + default: + { + measuredDefaultHeight = 48; + break; + } + } + } + + //-------------------------------------------------------------------------- + // + // Variables + // + //-------------------------------------------------------------------------- + + /** + * @copy spark.skins.spark.ApplicationSkin#hostComponent + */ + public var hostComponent:TextArea; // SkinnableComponent will populate + + //-------------------------------------------------------------------------- + // + // Overridden methods + // + //-------------------------------------------------------------------------- + + /** + * @private + */ + override protected function measure():void + { + super.measure(); + measureTextComponent(hostComponent); + } + + /** + * @private + */ + override protected function layoutContents(unscaledWidth:Number, + unscaledHeight:Number):void + { + // base class handles border position & size + super.layoutContents(unscaledWidth, unscaledHeight); + + // position & size the text + var paddingLeft:Number = getStyle("paddingLeft"); + var paddingRight:Number = getStyle("paddingRight"); + var paddingTop:Number = getStyle("paddingTop"); + var paddingBottom:Number = getStyle("paddingBottom"); + + var unscaledTextWidth:Number = Math.max(0, unscaledWidth - paddingLeft - paddingRight); + var unscaledTextHeight:Number = Math.max(0, unscaledHeight - paddingTop - paddingBottom); + + if (textDisplay) + { + var verticalPosAdjustment:Number = 0; + var heightAdjustment:Number = 0; + + /* if (Platform.isIOS) + { + verticalPosAdjustment = Math.min(iOSVerticalPaddingAdjustment, paddingTop); + heightAdjustment = verticalPosAdjustment + Math.min(iOSVerticalPaddingAdjustment, paddingBottom); + }*/ + + textDisplay.commitStyles(); + setElementSize(textDisplay, unscaledTextWidth, unscaledTextHeight + heightAdjustment); + setElementPosition(textDisplay, paddingLeft, paddingTop - verticalPosAdjustment); + } + + if (promptDisplay) + { + if (promptDisplay is StyleableTextField) + StyleableTextField(promptDisplay).commitStyles(); + + setElementSize(promptDisplay, unscaledTextWidth, unscaledTextHeight); + setElementPosition(promptDisplay, paddingLeft, paddingTop); + } + } + + override protected function createTextDisplay():IStyleableEditableText + { + return new ScrollableStageText(multiline); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/flex-sdk/blob/75236aa8/frameworks/projects/mobiletheme/src/spark/skins/android4/StageTextInputSkin.as ---------------------------------------------------------------------- diff --git a/frameworks/projects/mobiletheme/src/spark/skins/android4/StageTextInputSkin.as b/frameworks/projects/mobiletheme/src/spark/skins/android4/StageTextInputSkin.as new file mode 100644 index 0000000..7f92647 --- /dev/null +++ b/frameworks/projects/mobiletheme/src/spark/skins/android4/StageTextInputSkin.as @@ -0,0 +1,134 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// 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.android4 +{ + import spark.components.TextInput; + import spark.components.supportClasses.IStyleableEditableText; + import spark.components.supportClasses.ScrollableStageText; + import spark.components.supportClasses.StyleableTextField; + import spark.skins.android4.supportClasses.StageTextSkinBase; + + /** + * ActionScript-based skin for TextInput controls in mobile applications that uses a + * StyleableStageText class for the text input. + * + * @see spark.components.TextInput + * @see spark.components.supportClasses.StyleableStageText + * + * @langversion 3.0 + * @playerversion AIR 3.0 + * @productversion Flex 4.6 + */ + public class StageTextInputSkin extends StageTextSkinBase + { + //-------------------------------------------------------------------------- + // + // Constructor + // + //-------------------------------------------------------------------------- + + /** + * Constructor. + * + * @langversion 3.0 + * @playerversion AIR 3.0 + * @productversion Flex 4.6 + */ + public function StageTextInputSkin() + { + super(); + multiline = false; + } + + //-------------------------------------------------------------------------- + // + // Variables + // + //-------------------------------------------------------------------------- + + /** + * @copy spark.skins.spark.ApplicationSkin#hostComponent + */ + public var hostComponent:TextInput; // SkinnableComponent will populate + + //-------------------------------------------------------------------------- + // + // Overridden methods + // + //-------------------------------------------------------------------------- + + /** + * @private + */ + override protected function measure():void + { + super.measure(); + measureTextComponent(hostComponent); + } + + /** + * @private + */ + override protected function layoutContents(unscaledWidth:Number, + unscaledHeight:Number):void + { + // base class handles border position & size + super.layoutContents(unscaledWidth, unscaledHeight); + + // position & size the text + var paddingLeft:Number = getStyle("paddingLeft"); + var paddingRight:Number = getStyle("paddingRight"); + var paddingTop:Number = getStyle("paddingTop"); + var paddingBottom:Number = getStyle("paddingBottom"); + + var unscaledTextWidth:Number = Math.max(0, unscaledWidth - paddingLeft - paddingRight); + var unscaledTextHeight:Number = Math.max(0, unscaledHeight - paddingTop - paddingBottom); + + // default vertical positioning is centered + var textHeight:Number = getElementPreferredHeight(textDisplay); + var textY:Number = Math.round(0.5 * (unscaledTextHeight - textHeight)) + paddingTop; + + if (textDisplay) + { + textDisplay.commitStyles(); + setElementSize(textDisplay, unscaledTextWidth, unscaledTextHeight); + setElementPosition(textDisplay, paddingLeft, textY); + } + + if (promptDisplay) + { + if (promptDisplay is StyleableTextField) + StyleableTextField(promptDisplay).commitStyles(); + + var promptHeight:Number = getElementPreferredHeight(promptDisplay); + var promptY:Number = Math.round(0.5 * (unscaledTextHeight - promptHeight)) + paddingTop; + + setElementSize(promptDisplay, unscaledTextWidth, promptHeight); + setElementPosition(promptDisplay, paddingLeft, promptY); + } + } + + override protected function createTextDisplay():IStyleableEditableText + { + return new ScrollableStageText(multiline); + } + + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/flex-sdk/blob/75236aa8/frameworks/projects/mobiletheme/src/spark/skins/android4/TextAreaSkin.as ---------------------------------------------------------------------- diff --git a/frameworks/projects/mobiletheme/src/spark/skins/android4/TextAreaSkin.as b/frameworks/projects/mobiletheme/src/spark/skins/android4/TextAreaSkin.as new file mode 100644 index 0000000..0c231a6 --- /dev/null +++ b/frameworks/projects/mobiletheme/src/spark/skins/android4/TextAreaSkin.as @@ -0,0 +1,892 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// 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.android4 +{ + +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.android4.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 = 4; + flatheight = 9; + break; + } + case DPIClassification.DPI_480: + { + measuredDefaultWidth = 880; + measuredDefaultHeight = 140; + layoutBorderSize = 3; + flatheight = 7; + break; + } + case DPIClassification.DPI_320: + { + measuredDefaultWidth = 612; + measuredDefaultHeight = 106; + layoutBorderSize = 2; + flatheight = 6; + break; + } + case DPIClassification.DPI_240: + { + measuredDefaultWidth = 440; + measuredDefaultHeight = 70; + layoutBorderSize = 2; + flatheight = 5; + break; + } + case DPIClassification.DPI_120: + { + measuredDefaultWidth = 220; + measuredDefaultHeight = 35; + layoutBorderSize = 1; + flatheight = 2; + break; + } + default: + { + measuredDefaultWidth = 306; + measuredDefaultHeight = 53; + layoutBorderSize = 1; + flatheight = 3; + 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 flatheight: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 selectWidth:uint = isFocused ? layoutBorderSize + 1 : layoutBorderSize; + if (isNaN(contentBackgroundAlpha)) + { + contentBackgroundAlpha = 1; + } + if (getStyle("contentBackgroundBorder") == "flat") + { + var halfGap:int = flatheight * 2; + //background + graphics.beginFill(contentBackgroundColor, contentBackgroundAlpha); + graphics.drawRect(0, 0, unscaledWidth, unscaledHeight - flatheight); + graphics.endFill(); + //begin flat border + graphics.beginFill(borderColor, 1); + //left half border + graphics.drawRect(0, unscaledHeight - halfGap, selectWidth, flatheight ); + //bottom border + graphics.drawRect(0, unscaledHeight - flatheight, unscaledWidth, selectWidth); + //right border + graphics.drawRect(unscaledWidth - selectWidth, unscaledHeight - halfGap, selectWidth, flatheight); + graphics.endFill(); + } + else if (getStyle("contentBackgroundBorder") == "rectangle") + { + var borderWidth:uint = layoutBorderSize * 2; + //rectangle border and background + graphics.lineStyle(selectWidth, borderColor, 1); + graphics.beginFill(contentBackgroundColor, contentBackgroundAlpha); + graphics.drawRect(layoutBorderSize, layoutBorderSize, 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(); + } + +} +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/flex-sdk/blob/75236aa8/frameworks/projects/mobiletheme/src/spark/skins/android4/TextInputSkin.as ---------------------------------------------------------------------- diff --git a/frameworks/projects/mobiletheme/src/spark/skins/android4/TextInputSkin.as b/frameworks/projects/mobiletheme/src/spark/skins/android4/TextInputSkin.as new file mode 100644 index 0000000..5a7a35e --- /dev/null +++ b/frameworks/projects/mobiletheme/src/spark/skins/android4/TextInputSkin.as @@ -0,0 +1,376 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// 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.android4 +{ + import flash.events.Event; + import flash.events.FocusEvent; + import flash.events.SoftKeyboardEvent; + import flash.system.Capabilities; + + import mx.core.DPIClassification; + import mx.core.EventPriority; + import mx.core.mx_internal; + import mx.events.FlexEvent; + import mx.utils.Platform; + + import spark.components.TextInput; + import spark.components.supportClasses.StyleableTextField; + import spark.skins.android4.supportClasses.TextSkinBase; + + use namespace mx_internal; + + /** + * ActionScript-based skin for TextInput controls in mobile applications. + * + * @see spark.components.TextInput + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 2.5 + * @productversion Flex 4.5 + */ + public class TextInputSkin extends TextSkinBase + { + //-------------------------------------------------------------------------- + // + // Constructor + // + //-------------------------------------------------------------------------- + + /** + * Constructor. + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 2.5 + * @productversion Flex 4.5 + */ + public function TextInputSkin() + { + super(); + + // on iOS, make adjustments for native text rendering + _isIOS = Platform.isIOS; + + switch (applicationDPI) + { + case DPIClassification.DPI_640: + { + measuredDefaultWidth = 1200; + measuredDefaultHeight = 132; + layoutBorderSize = 4; + flatheight = 9; + break; + } + case DPIClassification.DPI_480: + { + + measuredDefaultWidth = 880; + measuredDefaultHeight = 100; + layoutBorderSize = 3; + flatheight = 7; + break; + } + case DPIClassification.DPI_320: + { + measuredDefaultWidth = 600; + measuredDefaultHeight = 66; + layoutBorderSize = 2; + flatheight = 6; + break; + } + case DPIClassification.DPI_240: + { + measuredDefaultWidth = 440; + measuredDefaultHeight = 50; + layoutBorderSize = 2; + flatheight = 5; + break; + } + case DPIClassification.DPI_120: + { + measuredDefaultWidth = 220; + measuredDefaultHeight = 25; + layoutBorderSize = 1; + flatheight = 2; + break; + } + default: + { + measuredDefaultWidth = 300; + measuredDefaultHeight = 33; + layoutBorderSize = 1; + flatheight = 3; + break; + } + } + addEventListener(FocusEvent.FOCUS_IN, focusChangeHandler); + addEventListener(FocusEvent.FOCUS_OUT, focusChangeHandler); + } + + //-------------------------------------------------------------------------- + // + // Variables + // + //-------------------------------------------------------------------------- + + protected var isFocused:Boolean = false; + + protected var flatheight:uint; + + /** + * @copy spark.skins.spark.ApplicationSkin#hostComponent + */ + public var hostComponent:TextInput; // SkinnableComponent will populate + + /** + * @private + */ + private var _isIOS:Boolean; + + /** + * @private + */ + private var _isEditing:Boolean; + + /** + * @private + */ + override protected function createChildren():void + { + super.createChildren(); + + textDisplay.addEventListener("editableChanged", editableChangedHandler); + textDisplay.addEventListener(FlexEvent.VALUE_COMMIT, valueCommitHandler); + + // remove hit area improvements on iOS when editing + if (_isIOS) + { + textDisplay.addEventListener(SoftKeyboardEvent.SOFT_KEYBOARD_ACTIVATING, textDisplay_softKeyboardActivatingHandler, false, EventPriority.DEFAULT_HANDLER); + textDisplay.addEventListener(SoftKeyboardEvent.SOFT_KEYBOARD_DEACTIVATE, textDisplay_softKeyboardDeactivateHandler); + } + } + + /** + * @private + */ + override protected function measure():void + { + super.measure(); + + var paddingLeft:Number = getStyle("paddingLeft"); + var paddingRight:Number = getStyle("paddingRight"); + var paddingTop:Number = getStyle("paddingTop"); + var paddingBottom:Number = getStyle("paddingBottom"); + var textHeight:Number = getStyle("fontSize") as Number; + + if (textDisplay) + { + // temporarily change text for measurement + var oldText:String = textDisplay.text; + + // commit styles so we can get a valid textHeight + textDisplay.text = "Wj"; + textDisplay.commitStyles(); + + textHeight = textDisplay.measuredTextSize.y; + textDisplay.text = oldText; + } + + // width is based on maxChars (if set) + if (hostComponent && hostComponent.maxChars) + { + // Grab the fontSize and subtract 2 as the pixel value for each character. + // This is just an approximation, but it appears to be a reasonable one + // for most input and most font. + var characterWidth:int = Math.max(1, (getStyle("fontSize") - 2)); + measuredWidth = (characterWidth * hostComponent.maxChars) + + paddingLeft + paddingRight + StyleableTextField.TEXT_WIDTH_PADDING; + } + + measuredHeight = paddingTop + textHeight + 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); + } + + // position & size the text + 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; + + // default vertical positioning is centered + var textHeight:Number = getElementPreferredHeight(textDisplay); + var textY:Number = Math.round(0.5 * (unscaledTextHeight - textHeight)) + paddingTop; + + // On iOS the TextField top and bottom edges are bounded by the padding. + // On all other platforms, the height of the textDisplay is + // textHeight + paddingBottom to increase hitArea on bottom. + // Note: We don't move the Y position upwards because TextField + // has way to set vertical positioning. + // Note: iOS is a special case due to the clear button provided by the + // native text control used while editing. + var adjustedTextHeight:Number = (_isIOS && _isEditing) ? textHeight : textHeight + paddingBottom; + + if (textDisplay) + { + // We're going to do a few tricks to try to increase the size of our hitArea to make it + // easier for users to select text or put the caret in a certain spot. To do that, + // rather than set textDisplay.x=paddingLeft, we are going to set + // textDisplay.leftMargin = paddingLeft. In addition, we're going to size the height + // of the textDisplay larger than just the size of the text inside to increase the hitArea + // on the bottom. We'll also assign textDisplay.rightMargin = paddingRight to increase the + // the hitArea on the right. Unfortunately, there's no way to increase the hitArea on the top + // just yet, but these three tricks definitely help out with regards to user experience. + // See http://bugs.adobe.com/jira/browse/SDK-29406 and http://bugs.adobe.com/jira/browse/SDK-29405 + + // set leftMargin, rightMargin to increase the hitArea. Need to set it before calling commitStyles(). + var marginChanged:Boolean = ((textDisplay.leftMargin != paddingLeft) || + (textDisplay.rightMargin != paddingRight)); + + textDisplay.leftMargin = paddingLeft; + textDisplay.rightMargin = paddingRight; + + // need to force a styleChanged() after setting leftMargin, rightMargin if they + // changed values. Then we can validate the styles through commitStyles() + if (marginChanged) + textDisplay.styleChanged(null); + textDisplay.commitStyles(); + + setElementSize(textDisplay, unscaledWidth, adjustedTextHeight); + + // set x=0 since we're using textDisplay.leftMargin = paddingLeft + setElementPosition(textDisplay, 0, textY); + } + + if (promptDisplay) + { + promptDisplay.commitStyles(); + setElementSize(promptDisplay, unscaledTextWidth, adjustedTextHeight); + setElementPosition(promptDisplay, paddingLeft, textY); + } + } + + 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 selectWidth:uint = isFocused ? layoutBorderSize + 1 : layoutBorderSize; + if (isNaN(contentBackgroundAlpha)) + { + contentBackgroundAlpha = 1; + } + if (getStyle("contentBackgroundBorder") == "flat") + { + var halfGap:int = flatheight * 2; + //background + graphics.beginFill(contentBackgroundColor, contentBackgroundAlpha); + graphics.drawRect(0, 0, unscaledWidth, unscaledHeight - flatheight); + graphics.endFill(); + //begin flat border + graphics.beginFill(borderColor, 1); + //left half border + graphics.drawRect(0, unscaledHeight - halfGap, selectWidth, flatheight ); + //bottom border + graphics.drawRect(0, unscaledHeight - flatheight, unscaledWidth, selectWidth); + //right border + graphics.drawRect(unscaledWidth - selectWidth, unscaledHeight - halfGap, selectWidth, flatheight); + graphics.endFill(); + } + else if (getStyle("contentBackgroundBorder") == "rectangle") + { + var borderWidth:uint = layoutBorderSize * 2; + //rectangle border and background + graphics.lineStyle(selectWidth, borderColor, 1); + graphics.beginFill(contentBackgroundColor, contentBackgroundAlpha); + graphics.drawRect(layoutBorderSize, layoutBorderSize, unscaledWidth - borderWidth, unscaledHeight - borderWidth); + graphics.endFill(); + } + } + + /** + * @private + */ + private function editableChangedHandler(event:Event):void + { + invalidateDisplayList(); + } + + /** + * @private + * The text changed in some way. + * + * Dynamic fields (ie !editable) with no text measure with width=0 and height=0. + * If the text changed, need to remeasure the text to get the correct height so it + * will be laid out correctly. + */ + private function valueCommitHandler(event:Event):void + { + if (textDisplay && !textDisplay.editable) + invalidateDisplayList(); + } + + /** + * @private + */ + private function textDisplay_softKeyboardActivatingHandler(event:SoftKeyboardEvent):void + { + if (event.isDefaultPrevented()) + return; + + _isEditing = true; + invalidateDisplayList(); + } + + /** + * @private + */ + private function textDisplay_softKeyboardDeactivateHandler(event:SoftKeyboardEvent):void + { + _isEditing = false; + invalidateDisplayList(); + } + + private function focusChangeHandler(event:FocusEvent):void + { + isFocused = event.type == FocusEvent.FOCUS_IN; + invalidateDisplayList(); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/flex-sdk/blob/75236aa8/frameworks/projects/mobiletheme/src/spark/skins/android4/TextSkinBase.as ---------------------------------------------------------------------- diff --git a/frameworks/projects/mobiletheme/src/spark/skins/android4/TextSkinBase.as b/frameworks/projects/mobiletheme/src/spark/skins/android4/TextSkinBase.as new file mode 100644 index 0000000..79ce852 --- /dev/null +++ b/frameworks/projects/mobiletheme/src/spark/skins/android4/TextSkinBase.as @@ -0,0 +1,212 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// 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.android4.supportClasses +{ + + import flash.display.DisplayObject; + + import mx.core.mx_internal; + + import spark.components.supportClasses.StyleableTextField; + + use namespace mx_internal; + + /** + * ActionScript-based skin for text input controls in mobile applications that + * uses a StyleableTextField class for the text display. + * + * @see spark.components.supportClasses.StyleableTextField + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 2.5 + * @productversion Flex 4.5 + */ + public class TextSkinBase extends MobileSkin + { + //-------------------------------------------------------------------------- + // + // Constructor + // + //-------------------------------------------------------------------------- + /** + * Constructor. + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 2.5 + * @productversion Flex 4.5 + * + */ + public function TextSkinBase() + { + super(); + } + + //-------------------------------------------------------------------------- + // + // Graphics variables + // + //-------------------------------------------------------------------------- + + /** + * Defines the border. + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 2.5 + * @productversion Flex 4.5 + */ + + //-------------------------------------------------------------------------- + // + // Layout variables + // + //-------------------------------------------------------------------------- + + /** + * Defines the corner radius. + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 2.5 + * @productversion Flex 4.5 + */ + + protected var layoutBorderSize:uint; + + //-------------------------------------------------------------------------- + // + // Variables + // + //-------------------------------------------------------------------------- + + /** + * @private + * + * Instance of the border graphics. + */ + protected var border:DisplayObject; + + private var borderVisibleChanged:Boolean = false; + + //-------------------------------------------------------------------------- + // + // Skin parts + // + //-------------------------------------------------------------------------- + + /** + * textDisplay skin part. + */ + public var textDisplay:StyleableTextField; + + [Bindable] + /** + * Bindable promptDisplay skin part. Bindings fire when promptDisplay is + * removed and added for proper updating by the SkinnableTextBase. + */ + public var promptDisplay:StyleableTextField; + + //-------------------------------------------------------------------------- + // + // Overridden methods + // + //-------------------------------------------------------------------------- + + /** + * @private + */ + override protected function createChildren():void + { + super.createChildren(); + + if (!textDisplay) + { + textDisplay = StyleableTextField(createInFontContext(StyleableTextField)); + textDisplay.styleName = this; + textDisplay.editable = true; + textDisplay.useTightTextBounds = false; + addChild(textDisplay); + } + } + + /** + * @private + */ + protected function createPromptDisplay():StyleableTextField + { + var prompt:StyleableTextField = StyleableTextField(createInFontContext(StyleableTextField)); + prompt.styleName = this; + prompt.editable = false; + prompt.mouseEnabled = false; + prompt.useTightTextBounds = false; + prompt.focusEnabled = false; + return prompt; + } + + /** + * @private + */ + + override public function styleChanged(styleProp:String):void + { + var allStyles:Boolean = !styleProp || styleProp == "styleName"; + + if (allStyles || styleProp == "borderVisible") + { + borderVisibleChanged = true; + invalidateProperties(); + } + + if (allStyles || styleProp.indexOf("padding") == 0) + { + invalidateDisplayList(); + } + + super.styleChanged(styleProp); + } + + /** + * @private + */ + override protected function commitCurrentState():void + { + super.commitCurrentState(); + + alpha = currentState.indexOf("disabled") == -1 ? 1 : 0.5; + + var showPrompt:Boolean = currentState.indexOf("WithPrompt") >= 0; + + if (showPrompt && !promptDisplay) + { + promptDisplay = createPromptDisplay(); + addChild(promptDisplay); + } + else if (!showPrompt && promptDisplay) + { + removeChild(promptDisplay); + promptDisplay = null; + } + + invalidateDisplayList(); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/flex-sdk/blob/75236aa8/frameworks/projects/mobiletheme/src/spark/skins/android4/supportClasses/StageTextSkinBase.as ---------------------------------------------------------------------- diff --git a/frameworks/projects/mobiletheme/src/spark/skins/android4/supportClasses/StageTextSkinBase.as b/frameworks/projects/mobiletheme/src/spark/skins/android4/supportClasses/StageTextSkinBase.as new file mode 100644 index 0000000..3dc1966 --- /dev/null +++ b/frameworks/projects/mobiletheme/src/spark/skins/android4/supportClasses/StageTextSkinBase.as @@ -0,0 +1,402 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// 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.android4.supportClasses +{ +import flash.display.DisplayObject; +import flash.events.FocusEvent; + +import mx.core.DPIClassification; +import mx.core.mx_internal; + +import spark.components.supportClasses.IStyleableEditableText; +import spark.components.supportClasses.SkinnableTextBase; +import spark.components.supportClasses.StyleableStageText; +import spark.components.supportClasses.StyleableTextField; +import spark.core.IDisplayText; +import spark.skins.mobile.supportClasses.MobileSkin; + +use namespace mx_internal; + +/** + * ActionScript-based skin for text input controls in mobile applications. + * + * @langversion 3.0 + * @playerversion AIR 3.0 + * @productversion Flex 4.6 + */ +public class StageTextSkinBase extends MobileSkin +{ + //-------------------------------------------------------------------------- + // + // Constructor + // + //-------------------------------------------------------------------------- + + /** + * Constructor. + * + * @langversion 3.0 + * @playerversion AIR 3.0 + * @productversion Flex 4.6 + * + */ + public function StageTextSkinBase() + { + super(); + + switch (applicationDPI) + { + case DPIClassification.DPI_640: + { + measuredDefaultWidth = 1200; + measuredDefaultHeight = 132; + layoutBorderSize = 4; + flatheight = 9; + break; + } + case DPIClassification.DPI_480: + { + measuredDefaultWidth = 880; + measuredDefaultHeight = 100; + layoutBorderSize = 3; + flatheight = 7; + break; + } + case DPIClassification.DPI_320: + { + measuredDefaultWidth = 600; + measuredDefaultHeight = 66; + layoutBorderSize = 2; + flatheight = 6; + break; + } + case DPIClassification.DPI_240: + { + measuredDefaultWidth = 440; + measuredDefaultHeight = 50; + layoutBorderSize = 2; + flatheight = 5; + break; + } + case DPIClassification.DPI_120: + { + measuredDefaultWidth = 220; + measuredDefaultHeight = 25; + layoutBorderSize = 1; + flatheight = 2; + break; + } + default: + { + measuredDefaultWidth = 300; + measuredDefaultHeight = 33; + layoutBorderSize = 1; + flatheight = 3; + break; + } + + } + addEventListener(FocusEvent.FOCUS_IN, focusChangeHandler); + addEventListener(FocusEvent.FOCUS_OUT, focusChangeHandler); + } + + //-------------------------------------------------------------------------- + // + // Graphics variables + // + //-------------------------------------------------------------------------- + + + //-------------------------------------------------------------------------- + // + // Layout variables + // + //-------------------------------------------------------------------------- + + /** + * Defines the border's thickness. + * + * @langversion 3.0 + * @playerversion AIR 3.0 + * @productversion Flex 4.6 + */ + protected var layoutBorderSize:uint; + + protected var flatheight:uint; + + //-------------------------------------------------------------------------- + // + // Variables + // + //-------------------------------------------------------------------------- + + protected var isFocused:Boolean = false; + + /** + * @private + * + * Instance of the border graphics. + */ + protected var border:DisplayObject; + + private var borderVisibleChanged:Boolean = false; + + /** + * @private + * + * Multiline flag. + */ + protected var multiline:Boolean = false; + + //-------------------------------------------------------------------------- + // + // Skin parts + // + //-------------------------------------------------------------------------- + + /** + * textDisplay skin part. + * + * @langversion 3.0 + * @playerversion AIR 3.0 + * @productversion Flex 4.6 + */ + public var textDisplay:IStyleableEditableText; + + [Bindable] + /** + * Bindable promptDisplay skin part. Bindings fire when promptDisplay is + * removed and added for proper updating by the SkinnableTextBase. + * + * @langversion 3.0 + * @playerversion AIR 3.0 + * @productversion Flex 4.6 + */ + public var promptDisplay:IDisplayText; + + //-------------------------------------------------------------------------- + // + // Overridden methods + // + //-------------------------------------------------------------------------- + + /** + * @private + */ + override protected function createChildren():void + { + super.createChildren(); + + if (!textDisplay) + { + textDisplay = createTextDisplay(); + textDisplay.editable = true; + textDisplay.styleName = this; + this.addChild(DisplayObject(textDisplay)); + } + } + + /** Could be overridden by subclasses + * + * @return instance of IStyleableEditableText + */ + protected function createTextDisplay():IStyleableEditableText + { + return new StyleableStageText(multiline); + } + + /** + * @private + */ + 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 selectWidth:uint = isFocused ? layoutBorderSize + 1 : layoutBorderSize; + if (isNaN(contentBackgroundAlpha)) + { + contentBackgroundAlpha = 1; + } + var halfGap:int = flatheight * 2; + // change the border type + if (getStyle("contentBackgroundBorder") == "flat") + { + //background + graphics.beginFill(contentBackgroundColor, contentBackgroundAlpha); + graphics.drawRect(0, 0, unscaledWidth, unscaledHeight - flatheight); + graphics.endFill(); + //begin flat border + graphics.beginFill(borderColor, 1); + //left half border + graphics.drawRect(0, unscaledHeight - halfGap, selectWidth, flatheight ); + //bottom border + graphics.drawRect(0, unscaledHeight - flatheight, unscaledWidth, selectWidth); + //right border + graphics.drawRect(unscaledWidth - selectWidth, unscaledHeight - halfGap, selectWidth, flatheight); + graphics.endFill(); + } + else if (getStyle("contentBackgroundBorder") == "rectangle") + { + var borderWidth:uint = layoutBorderSize * 2; + //rectangle border and background + graphics.lineStyle(selectWidth, borderColor, 1); + graphics.beginFill(contentBackgroundColor, contentBackgroundAlpha); + graphics.drawRect(layoutBorderSize, layoutBorderSize, unscaledWidth - borderWidth, unscaledHeight - borderWidth); + graphics.endFill(); + } + } + + /** + * @private + */ + override public function styleChanged(styleProp:String):void + { + var allStyles:Boolean = !styleProp || styleProp == "styleName"; + + if (allStyles || styleProp == "borderVisible") + { + borderVisibleChanged = true; + invalidateProperties(); + } + + if (allStyles || styleProp.indexOf("padding") == 0) + { + invalidateDisplayList(); + } + super.styleChanged(styleProp); + } + + /** + * @private + */ + override protected function commitCurrentState():void + { + super.commitCurrentState(); + + alpha = currentState.indexOf("disabled") == -1 ? 1 : 0.5; + + var showPrompt:Boolean = currentState.indexOf("WithPrompt") != -1; + + if (showPrompt && !promptDisplay) + { + promptDisplay = createPromptDisplay(); + promptDisplay.addEventListener(FocusEvent.FOCUS_IN, promptDisplay_focusInHandler); + } + else if (!showPrompt && promptDisplay) + { + promptDisplay.removeEventListener(FocusEvent.FOCUS_IN, promptDisplay_focusInHandler); + removeChild(promptDisplay as DisplayObject); + promptDisplay = null; + } + super.commitCurrentState(); + + invalidateDisplayList(); + } + + //-------------------------------------------------------------------------- + // + // Methods + // + //-------------------------------------------------------------------------- + + /** + * @private + * Create a control appropriate for displaying the prompt text in a mobile + * input field. + */ + protected function createPromptDisplay():IDisplayText + { + var prompt:StyleableTextField = StyleableTextField(createInFontContext(StyleableTextField)); + prompt.styleName = this; + prompt.editable = false; + prompt.mouseEnabled = false; + prompt.useTightTextBounds = false; + // StageText objects appear in their own layer on top of the display + // list. So, even though this prompt may be created after the StageText + // for textDisplay, textDisplay will still be on top. + addChild(prompt); + + return prompt; + } + + /** + * @private + * Utility function used by subclasses' measure functions to measure their + * text host components. + */ + protected function measureTextComponent(hostComponent:SkinnableTextBase):void + { + var paddingLeft:Number = getStyle("paddingLeft"); + var paddingRight:Number = getStyle("paddingRight"); + var paddingTop:Number = getStyle("paddingTop"); + var paddingBottom:Number = getStyle("paddingBottom"); + var textHeight:Number = getStyle("fontSize"); + + if (textDisplay) + { + textHeight = getElementPreferredHeight(textDisplay); + } + // width is based on maxChars (if set) + if (hostComponent && hostComponent.maxChars) + { + // Grab the fontSize and subtract 2 as the pixel value for each character. + // This is just an approximation, but it appears to be a reasonable one + // for most input and most font. + var characterWidth:int = Math.max(1, (textHeight - 2)); + measuredWidth = (characterWidth * hostComponent.maxChars) + paddingLeft + paddingRight; + } + + measuredHeight = paddingTop + textHeight + paddingBottom; + } + + //-------------------------------------------------------------------------- + // + // Event handlers + // + //-------------------------------------------------------------------------- + + /** + * Listen to see if the component gains focus then change the style to selected + */ + private function focusChangeHandler(event:FocusEvent):void + { + isFocused = event.type == FocusEvent.FOCUS_IN; + invalidateDisplayList(); + } + + /** + * If the prompt is focused, we need to move focus to the textDisplay + * StageText. This needs to happen outside of the process of setting focus + * to the prompt, so we use callLater to do that. + */ + private function focusTextDisplay():void + { + textDisplay.setFocus(); + } + + private function promptDisplay_focusInHandler(event:FocusEvent):void + { + callLater(focusTextDisplay); + } +} +} \ No newline at end of file