//////////////////////////////////////////////////////////////////////////////// // // 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 org.apache.spark.components.supportClasses { import flash.display.DisplayObject; import flash.display.DisplayObjectContainer; import flash.display.Stage; import flash.events.Event; import flash.events.MouseEvent; import flash.events.SoftKeyboardEvent; import flash.events.SoftKeyboardTrigger; import flash.events.TimerEvent; import flash.geom.Rectangle; import flash.utils.Timer; import mx.core.EventPriority; import mx.core.FlexGlobals; import mx.core.FlexVersion; import mx.core.mx_internal; import mx.effects.IEffect; import mx.effects.Parallel; import mx.events.EffectEvent; import mx.events.FlexEvent; import mx.events.ResizeEvent; import mx.events.SandboxMouseEvent; import mx.managers.PopUpManager; import mx.managers.SystemManager; import mx.styles.StyleProtoChain; import spark.components.Application; import spark.components.supportClasses.SkinnableComponent; import spark.effects.Move; import spark.effects.Resize; import spark.effects.animation.Animation; import spark.effects.easing.IEaser; import spark.effects.easing.Power; import spark.events.PopUpEvent; use namespace mx_internal; //-------------------------------------- // Events //-------------------------------------- /** * Dispatched by the container when it's opened and ready for user interaction. * *

This event is dispatched when the container switches from the * closed to normal skin state and the transition * to that state completes.

* *

Note: As of Flex 4.6, SkinnablePopUp container inherits its styles * from the stage and not its owner.

* * @eventType spark.events.PopUpEvent.OPEN * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ [Event(name="open", type="spark.events.PopUpEvent")] /** * Dispatched by the container when it's closed. * *

This event is dispatched when the container switches from the * normal to closed skin state and * the transition to that state completes.

* *

The event provides a mechanism to pass commit information from * the container to an event listener. * One typical usage scenario is building a multiple-choice dialog with a * cancel button. * When a valid option is selected, you close the pop up * with a call to the close() method, passing * true to the commit parameter and optionally passing in * any relevant data. * When the SkinnablePopUpComponent has completed closing, * it dispatches this event. * Then, in the event listener, you can check the commit * property and perform the appropriate action.

* * @eventType spark.events.PopUpEvent.CLOSE * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ [Event(name="close", type="spark.events.PopUpEvent")] //-------------------------------------- // Styles //-------------------------------------- /** * Duration of the soft keyboard move and resize effect in milliseconds. * * @default 150 * * @langversion 3.0 * @playerversion Flash 11 * @playerversion AIR 3.1 * @productversion Flex 4.6 */ [Style(name="softKeyboardEffectDuration", type="Number", format="Time", inherit="no", minValue="0.0")] /** * The amount of transparency to apply to the modal window. * * @default 0.5 * * @langversion 3.0 * @playerversion Flash 11 * @playerversion AIR 3.1 * @productversion Flex 4.6 */ // [Style(name="modalTransparency", type="Number", inherit="yes", theme="spark, mobile", minValue="0.0", maxValue="1.0")] /** * The amout of blur to apply to the content below the modal transparency. * * @default 3 * * @langversion 3.0 * @playerversion Flash 11 * @playerversion AIR 3.1 * @productversion Flex 4.6 */ // [Style(name="modalTransparencyBlur", type="Number", format="Time", inherit="no", minValue="0.0")] /** * The color of the modal transparency. * * @default #DDDDDD * * @langversion 3.0 * @playerversion Flash 11 * @playerversion AIR 3.1 * @productversion Flex 4.6 */ // [Style(name="modalTransparencyColor", type="uint", format="Color", inherit="no", theme="spark, mobile")] /** * Duration of the of the modal backgrounds transition in milliseconds. * * @default 100 * * @langversion 3.0 * @playerversion Flash 11 * @playerversion AIR 3.1 * @productversion Flex 4.6 */ // [Style(name="modalTransparencyDuration", type="Time", inherit="no", theme="spark, mobile", minValue="0.0")] //-------------------------------------- // States //-------------------------------------- /** * Normal State * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ [SkinState("normal")] /** * The closed state. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ [SkinState("closed")] /** * The SkinnableComponent class defines the base class for skinnable components that function as pop-ups. * The skins used by a SkinnableComponent class are typically child classes of * the Skin class. * *

Associate a skin class with a component class by setting the skinClass style property of the * component class. You can set the skinClass property in CSS, as the following example shows:

* *
MyComponent
	 *  {
	 *    skinClass: ClassReference("my.skins.MyComponentSkin")
	 *  }
* *

The following example sets the skinClass property in MXML:

* *
	 *  <MyComponent skinClass="my.skins.MyComponentSkin"/>
* *

The SkinnablePopUpComponent container has the following default characteristics:

* * * * * * *
CharacteristicDescription
Default sizeLarge enough to display its children
Minimum size0 pixels
Maximum size10000 pixels wide and 10000 pixels high
Default skin classspark.skins.spark.SkinnablePopUpComponentSkin
* * @mxml

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

* *
	 *  <s:SkinnablePopUpComponent
	 *    Events
	 *    close="No default"
	 *    open="No default"
	 *  />
	 *  
* * @see spark.components.supportClasses.Skin * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public class SkinnablePopUpComponent extends SkinnableComponent { //-------------------------------------------------------------------------- // // Constructor // //-------------------------------------------------------------------------- /** * Constructor. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function SkinnablePopUpComponent() { super(); } //-------------------------------------------------------------------------- // // Variables // //-------------------------------------------------------------------------- /** * Storage for the close event while waiting for the close transition to * complete before dispatching it. * * @private */ private var closeEvent:PopUpEvent; /** * Track whether the container is added to the PopUpManager. * * @private */ private var addedToPopUpManager:Boolean = false; //-------------------------------------------------------------------------- // // Soft Keyboard Effect Variables // //-------------------------------------------------------------------------- /** * @private * The current soft keyboard effect instance. This field is only set in * cases where the position and size are not snapped during (a) initial * activation and (b) deactivation. */ private var softKeyboardEffect:IEffect; /** * @private * Original pop-up y-position. */ private var softKeyboardEffectCachedYPosition:Number; /** * @private * Indicates a soft keyboard event was received but the effect is delayed * while waiting for a mouseDown and mouseUp event sequence. */ private var softKeyboardEffectPendingEventType:String = null; /** * @private * Number of milliseconds to wait for a mouseDown and mouseUp event * sequence before playing the deactivate effect. */ mx_internal var softKeyboardEffectPendingEffectDelay:Number = 100; /** * @private * Timer used for awaiting a mouseDown event after a pending soft keyboard * effect. */ private var softKeyboardEffectPendingEventTimer:Timer; /** * @private * Flag when orientationChanging is dispatched and orientationChange has * not been received. */ private var softKeyboardEffectOrientationChanging:Boolean = false; /** * @private * Flag when orientation change handlers are installed to suppress * excess soft keyboard effects during orientation change on iOS. */ private var softKeyboardEffectOrientationHandlerAdded:Boolean = false; /** * @private * Flag when mouse and orientation listeners are installed before * the soft keyboard activate effect is played. */ private var softKeyboardStateChangeListenersInstalled:Boolean; /** * @private * Flag when resize listers are installed after ACTIVATE and uninstalled * just before DEACTIVATE. */ private var resizeListenerInstalled:Boolean = false; //-------------------------------------------------------------------------- // // Properties // //-------------------------------------------------------------------------- //---------------------------------- // centerPopUp //---------------------------------- /** * Storage for the centerPopUp property. * @private */ private var _centerPopUp:Boolean; /** * Whether or not the popup should center itself. * * @default false * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function get centerPopUp():Boolean { return _centerPopUp; } /** * @private */ public function set centerPopUp( value:Boolean ):void { if( _centerPopUp == value ) return; _centerPopUp = value; if( _centerPopUp ) { if( _stage ) _stage.addEventListener( Event.RESIZE, stage_resizeHandler, false, 0, true ); updatePopUpPosition(); } else { if( _stage ) _stage.removeEventListener( Event.RESIZE, stage_resizeHandler, false ); } } //---------------------------------- // isOpen //---------------------------------- /** * Storage for the isOpen property. * * @private */ private var _isOpen:Boolean = false; [Inspectable(category="General", defaultValue="false")] [Bindable(event="open")] [Bindable(event="close")] /** * Contains true when the container is open and is currently showing as a pop-up. * * @see #open * @see #close * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function get isOpen():Boolean { return _isOpen; } /** * Updates the isOpen flag to be reflected in the skin state * without actually popping up the container through the PopUpManager. * * @private */ mx_internal function setIsOpen(value:Boolean):void { // NOTE: DesignView relies on this API, consult tooling before making changes. _isOpen = value; invalidateSkinState(); } //---------------------------------- // resizeForSoftKeyboard //---------------------------------- private var _resizeForSoftKeyboard:Boolean = true; /** * Enables resizing the pop-up when the soft keyboard * on a mobile device is active. * * @default true * * @langversion 3.0 * @playerversion AIR 3 * @productversion Flex 4.6 */ public function get resizeForSoftKeyboard():Boolean { return _resizeForSoftKeyboard; } /** * @private */ public function set resizeForSoftKeyboard(value:Boolean):void { if (_resizeForSoftKeyboard == value) return; _resizeForSoftKeyboard = value; } //---------------------------------- // moveForSoftKeyboard //---------------------------------- private var _moveForSoftKeyboard:Boolean = true; /** * Enables moving the pop-up when the soft keyboard * on a mobile device is active. * * @default true * * @langversion 3.0 * @playerversion AIR 3 * @productversion Flex 4.6 */ public function get moveForSoftKeyboard():Boolean { return _moveForSoftKeyboard; } /** * @private */ public function set moveForSoftKeyboard(value:Boolean):void { if (_moveForSoftKeyboard == value) return; _moveForSoftKeyboard = value; } //-------------------------------------------------------------------------- // // Methods // //-------------------------------------------------------------------------- private var _stage:Stage; /** * Opens the container as a pop-up, and switches the skin state from * closed to normal. * After and transitions finish playing, it dispatches the * FlexEvent.OPEN event. * * @param owner The owner of the container. * The popup appears over this component. The owner must not be descendant * of this container. * * @param modal Whether the container should be modal. * A modal container takes all keyboard and mouse input until it is closed. * A nonmodal container allows other components to accept input while the pop-up window is open. * * @see #close * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function open(owner:DisplayObjectContainer, modal:Boolean = false):void { if (isOpen) return; closeEvent = null; // Clear any pending close event this.owner = owner; // We track whether we've been added to the PopUpManager to handle the // scenario of state transition interrupton. For example we may be playing // a close transition and be interrupted with a call to open() in which // case we wouldn't have removed the container from the PopUpManager since // the close transition never reached its end. if (!addedToPopUpManager) { addedToPopUpManager = true; // This will create the skin and attach it PopUpManager.addPopUp(this, owner, modal); _stage = stage; if( centerPopUp ) _stage.addEventListener( Event.RESIZE, stage_resizeHandler, false, 0, true ); updatePopUpPosition(); } // Change state *after* we pop up, as the skin needs to go be in the initial "closed" // state while being created above in order for transitions to detect state change and play. _isOpen = true; invalidateSkinState(); if (skin) skin.addEventListener(FlexEvent.STATE_CHANGE_COMPLETE, stateChangeComplete_handler); else stateChangeComplete_handler(null); // Call directly } /** * Positions the pop-up after the pop-up is added to PopUpManager but * before any state transitions occur. The base implementation of open() * calls updatePopUpPosition() immediately after the pop-up is added. * * This method may also be called at any time to update the pop-up's * position. Pop-ups that are positioned relative to their owner should * call this method after position or size changes occur on the owner or * it's ancestors. * * @langversion 3.0 * @playerversion AIR 3 * @productversion Flex 4.6 */ public function updatePopUpPosition():void { if( centerPopUp ) PopUpManager.centerPopUp( this ); } /** * Changes the current skin state to closed, waits until any state transitions * finish playing, dispatches a PopUpEvent.CLOSE event, * and then removes the container from the PopUpManager. * *

Use the close() method of the SkinnablePopUpComponent container * to pass data back to the main application from the pop up. * One typical usage scenario is building a dialog with a cancel button. * When a valid option is selected in the dialog box, you close the dialog * with a call to the close() method, passing * true to the commit parameter and optionally passing * any relevant data. * When the SkinnablePopUpComponent has completed closing, * it dispatch the close event. * In the event listener for the close event, you can check * the commit parameter and perform the appropriate actions.

* * @param commit Specifies if the return data should be committed by the application. * The value of this argument is written to the commit property of * the PopUpEvent event object. * * @param data Specifies any data returned to the application. * The value of this argument is written to the data property of * the PopUpEvent event object. * * @see #open * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function close(commit:Boolean = false, data:* = undefined):void { if (!isOpen) return; if( _stage ) _stage.removeEventListener( Event.RESIZE, stage_resizeHandler, false ); _stage = null; // We will dispatch the event later, when the close transition is complete. closeEvent = new PopUpEvent(PopUpEvent.CLOSE, false, false, commit, data); // Change state _isOpen = false; invalidateSkinState(); if (skin) skin.addEventListener(FlexEvent.STATE_CHANGE_COMPLETE, stateChangeComplete_handler); else stateChangeComplete_handler(null); // Call directly } /** * Called by the soft keyboard activate and deactive event handlers, * this method is responsible for creating the Spark effect played on the pop-up. * * This method may be overridden by subclasses. By default, it * creates a parellel move and resize effect on the pop-up. * * @param yTo The new y-coordinate of the pop-up. * * @param height The new height of the pop-up. * * @return An IEffect instance serving as the move and/or resize transition * for the pop-up. This effect is played after the soft keyboard is * activated or deactivated. * * @langversion 3.0 * @playerversion AIR 3 * @productversion Flex 4.6 */ protected function createSoftKeyboardEffect(yTo:Number, heightTo:Number):IEffect { var move:Move; var resize:Resize; var easer:IEaser = new Power(0, 5); if (yTo != this.y) { move = new Move(); move.target = this; move.yTo = yTo; move.disableLayout = true; move.easer = easer; } if (heightTo != this.height) { resize = new Resize(); resize.target = this; resize.heightTo = heightTo; resize.disableLayout = true; resize.easer = easer; } if (move && resize) { var parallel:Parallel = new Parallel(); parallel.addChild(move); parallel.addChild(resize); return parallel; } else if (move || resize) { return (move) ? move : resize; } return null; } //-------------------------------------------------------------------------- // // mx_internal properties // //-------------------------------------------------------------------------- //---------------------------------------- // softKeyboardEffectCachedExplicitHeight //---------------------------------------- /** * @private */ private var _softKeyboardEffectExplicitHeightFlag:Boolean = false; /** * @private * Flag when explicitHeight is set when the soft keyboard effect is * active. Use this to distinguish explicitHeight changes due to the * resizeForSoftKeyboard setting. When true, we prevent the original * cached height from being modified. */ mx_internal function get softKeyboardEffectExplicitHeightFlag():Boolean { return _softKeyboardEffectExplicitHeightFlag; } private function setSoftKeyboardEffectExplicitHeightFlag(value:Boolean):void { _softKeyboardEffectExplicitHeightFlag = value; } //---------------------------------------- // softKeyboardEffectCachedExplicitWidth //---------------------------------------- /** * @private */ private var _softKeyboardEffectExplicitWidthFlag:Boolean = false; /** * @private * Flag when explicitWidth is set when the soft keyboard effect is * active. Use this to distinguish explicitWidth changes due to the * resizeForSoftKeyboard setting. */ mx_internal function get softKeyboardEffectExplicitWidthFlag():Boolean { return _softKeyboardEffectExplicitWidthFlag; } private function setSoftKeyboardEffectExplicitWidthFlag(value:Boolean):void { _softKeyboardEffectExplicitWidthFlag = value; } //---------------------------------- // softKeyboardEffectCachedHeight //---------------------------------- private var _softKeyboardEffectCachedHeight:Number; /** * @private * The original pop-up height to restore to when the soft keyboard is * deactivated. If an explicitHeight was defined at activation, use it. * If not, then use explicitMaxHeight or measuredHeight. */ mx_internal function get softKeyboardEffectCachedHeight():Number { var heightTo:Number = _softKeyboardEffectCachedHeight; if (!softKeyboardEffectExplicitHeightFlag) { if (!isNaN(explicitMaxHeight) && (measuredHeight > explicitMaxHeight)) heightTo = explicitMaxHeight; else heightTo = measuredHeight; } return heightTo; } /** * @private */ private function setSoftKeyboardEffectCachedHeight(value:Number):void { // Only allow changes to the cached height if it was not set explicitly // prior to and/or during the soft keyboard effect. if (!softKeyboardEffectExplicitHeightFlag) _softKeyboardEffectCachedHeight = value; } //---------------------------------- // isSoftKeyboardEffectActive //---------------------------------- private var _isSoftKeyboardEffectActive:Boolean; /** * @private * Returns true if the soft keyboard is active and the pop-up is moved * and/or resized. */ mx_internal function get isSoftKeyboardEffectActive():Boolean { return _isSoftKeyboardEffectActive; } //---------------------------------- // marginTop //---------------------------------- private var _marginTop:Number = 0; /** * @private * Defines a margin at the top of the screen where the pop-up cannot be * resized or moved to. */ mx_internal function get softKeyboardEffectMarginTop():Number { return _marginTop; } /** * @private */ mx_internal function set softKeyboardEffectMarginTop(value:Number):void { _marginTop = value; } //---------------------------------- // marginBottom //---------------------------------- private var _marginBottom:Number = 0; /** * @private * Defines a margin at the bottom of the screen where the pop-up cannot be * resized or moved to. */ mx_internal function get softKeyboardEffectMarginBottom():Number { return _marginBottom; } /** * @private */ mx_internal function set softKeyboardEffectMarginBottom(value:Number):void { _marginBottom = value; } //---------------------------------- // isMouseDown //---------------------------------- private var _isMouseDown:Boolean = false; /** * @private */ private function get isMouseDown():Boolean { return _isMouseDown; } /** * @private */ private function set isMouseDown(value:Boolean):void { _isMouseDown = value; // Attempt to play a pending effect playPendingEffect(true); } //-------------------------------------------------------------------------- // // Overridden Methods // //-------------------------------------------------------------------------- /** * @private * Force callout inheritance chain to start at the style root. */ override mx_internal function initProtoChain():void { // Maintain backwards compatibility of popup style inheritance if (FlexVersion.compatibilityVersion < FlexVersion.VERSION_4_6) super.initProtoChain(); else StyleProtoChain.initProtoChain(this, false); } /** * @private */ override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void { super.updateDisplayList(unscaledWidth, unscaledHeight); if( centerPopUp ) updatePopUpPosition(); } /** * @private */ override protected function getCurrentSkinState():String { // The states are: // "normal" // "closed" return isOpen ? "normal" : "closed"; } //-------------------------------------------------------------------------- // // Event handlers // //-------------------------------------------------------------------------- /** * @private * Play the soft keyboard effect. */ private function startEffect(event:Event):void { removeEventListener(Event.ENTER_FRAME, startEffect); // Abort the effect if the pop-up is closed or closing. The state // transition handler will restore the original size of the pop-up. if (!isOpen || !softKeyboardEffect) return; // Clear the cached positions when the deactivate effect is complete. softKeyboardEffect.addEventListener(EffectEvent.EFFECT_END, softKeyboardEffectCleanup); softKeyboardEffect.addEventListener(EffectEvent.EFFECT_STOP, softKeyboardEffectCleanup); // Install mouse delay and orientation change listeners now. // explicitHeight listener is installed after the effect completes. if (isSoftKeyboardEffectActive) installSoftKeyboardStateChangeListeners(); // Force the master clock of the animation engine to update its // current time so that the overhead of creating the effect is not // included in our animation interpolation. See SDK-27793 Animation.pulse(); softKeyboardEffect.play(); } /** * @private * * Called when we have completed transitioning to opened/closed state. */ private function stateChangeComplete_handler(event:Event):void { // We get called directly with null if there's no skin to listen to. if (event) event.target.removeEventListener(FlexEvent.STATE_CHANGE_COMPLETE, stateChangeComplete_handler); // Check for soft keyboard support var topLevelApp:Application = FlexGlobals.topLevelApplication as Application; var softKeyboardEffectEnabled:Boolean = (topLevelApp && Application.softKeyboardBehavior == "none"); var smStage:Stage = systemManager.stage; if (isOpen) { dispatchEvent(new PopUpEvent(PopUpEvent.OPEN, false, false)); if (softKeyboardEffectEnabled) { if (smStage) { // Install soft keyboard event handling on the stage smStage.addEventListener(SoftKeyboardEvent.SOFT_KEYBOARD_ACTIVATE, stage_softKeyboardActivateHandler, true, EventPriority.DEFAULT, true); smStage.addEventListener(SoftKeyboardEvent.SOFT_KEYBOARD_DEACTIVATE, stage_softKeyboardDeactivateHandler, true, EventPriority.DEFAULT, true); // Use lower priority listener to allow subclasses to act // on the resize event before the soft keyboard effect does. systemManager.addEventListener(Event.RESIZE, systemManager_resizeHandler, false, EventPriority.EFFECT); updateSoftKeyboardEffect(true); } } } else { // Dispatch the close event before removing from the PopUpManager. dispatchEvent(closeEvent); closeEvent = null; if (softKeyboardEffectEnabled && smStage) { // Uninstall soft keyboard event handling smStage.removeEventListener(SoftKeyboardEvent.SOFT_KEYBOARD_ACTIVATE, stage_softKeyboardActivateHandler, true); smStage.removeEventListener(SoftKeyboardEvent.SOFT_KEYBOARD_DEACTIVATE, stage_softKeyboardDeactivateHandler, true); systemManager.removeEventListener(Event.RESIZE, systemManager_resizeHandler); } // We just finished closing, remove from the PopUpManager. PopUpManager.removePopUp(this); addedToPopUpManager = false; owner = null; // Position and size may be invalid if the close transition // completes before (a) deactivate is fired or (b) a pending // soft keyboard change is still queued. Update immediately. updateSoftKeyboardEffect(true); } } /** * @private */ private function stage_softKeyboardActivateHandler(event:SoftKeyboardEvent=null):void { var isFirstActivate:Boolean = false; // Reset state softKeyboardEffectPendingEventType = null; // Save the original y-position and height if this is the first // ACTIVATE event and an existing effect is not already in progress. if (!isSoftKeyboardEffectActive && !softKeyboardEffect) isFirstActivate = true; if (isFirstActivate) { // Play the activate effect with animation updateSoftKeyboardEffect(false); } else { // Keyboard height has changed. However, the effect can be delayed if // a mouseUp within the pop-up is pending. An additional activate can // occur while the keyboard is open to reflect size changes due to auto // correction or orientation changes. // As in the deactivate case, the move and resize effects should be // delayed until the user can complete any mouse interaction. This // allows the size and position of the pop-up to stay stable allowing // events like a button press to complete normally. if (isMouseDown) setPendingSoftKeyboardEvent(event); else updateSoftKeyboardEffect(true); } } /** * @private */ private function stage_softKeyboardDeactivateHandler(event:SoftKeyboardEvent=null):void { // If the effect is not active (no move or resize was needed), do // nothing. If we're in the middle of an orientation change, also do // nothing. if (!isSoftKeyboardEffectActive || softKeyboardEffectOrientationChanging) return; // Reset state softKeyboardEffectPendingEventType = null; if (event.triggerType == SoftKeyboardTrigger.USER_TRIGGERED) { // userTriggered indicates they keyboard was closed explicitly (soft // button on soft keyboard) or on Android, pressing the back button. // Play the deactivate effect immediately. updateSoftKeyboardEffect(false); } else // if (event.triggerType == SoftKeyboardTrigger.CONTENT_TRIGGERED) { // contentTriggered indicates focus was lost by tapping away from // StageText or a programmatic call. Unfortunately, this // distinction isn't entirely intuitive. We only care about delaying // the deactivate effect when due to a mouse event. Delaying the // effect allows the pop-up position and size to stay static until // any mouse interaction is complete (e.g. button click). // However, the softKeyboardDeactivate event is fired before // the mouseDown event: // deactivate -> mouseDown -> mouseUp // The approach here is to assume that a mouseDown was the trigger // for the softKeyboardDeactivate event. Continue to delay the // deactivate effect until a mouseDown and mouseUp sequence is // received. In the event that the deactivation was due to a // programmatic call, we'll stop this process after a specified // delay time. // If, in the future, the event order changes to either: // (a) mouseDown -> deactivate -> mouseUp // (b) mouseDown -> mouseUp -> deactivate // this approach will still work for the button click use case. // Sequence (b) would simply fire a normal button click and have // the consequence of a delayed deactivate effect only. setPendingSoftKeyboardEvent(event); } } /** * @private * Disable soft keyboard deactivate when orientation is changing. */ private function stage_orientationChangingHandler(event:Event):void { softKeyboardEffectOrientationChanging = true; } /** * @private * Re-enable soft keyboard deactivate effect when orietation change * completes. */ private function stage_orientationChangeHandler(event:Event):void { softKeyboardEffectOrientationChanging = false; } /** * @private * Listens for mouse events while the soft keyboard effect is active. */ private function mouseHandler(event:MouseEvent):void { isMouseDown = (event.type == MouseEvent.MOUSE_DOWN); } private function systemManager_mouseUpHandler(event:Event):void { isMouseDown = false; } /** * @private * This function is only called when the pendingEffectTimer completed and no * mouseDown and mouseUp sequence was fired. */ private function pendingEffectTimer_timerCompleteHandler(event:Event):void { playPendingEffect(false) } /** * @private * Play the effect when (a) not triggered by a mouse event or * (b) triggered by a mouseUp event */ private function playPendingEffect(isMouseEvent:Boolean):void { var isTimerRunning:Boolean = softKeyboardEffectPendingEventTimer && softKeyboardEffectPendingEventTimer.running; // Received a mouseDown event while the timer is still running. Stop // the timer and wait for the next mouseUp. var mouseDownDuringTimer:Boolean = isTimerRunning && (isMouseEvent && isMouseDown); // Cleanup the timer if we're (a) waiting for the next mouseUp or // (b) this function was called for timerComplete if (softKeyboardEffectPendingEventTimer && (mouseDownDuringTimer || !isMouseEvent)) { softKeyboardEffectPendingEventTimer.removeEventListener(TimerEvent.TIMER_COMPLETE, pendingEffectTimer_timerCompleteHandler); softKeyboardEffectPendingEventTimer.stop(); softKeyboardEffectPendingEventTimer = null; // If we caught a mouse down during the timer, we wait for the next // mouseUp event. There is no effect to play. If this isn't a mouse // event and the timer completed, then fall through and play the // pending effect. if (mouseDownDuringTimer) return; } // Sanity check that a pendingEvent still exists and that the pop-up // is currently open. If we timed out or if we got a mouseUp, then // allow the pending effect to play. var canPlayEffect:Boolean = softKeyboardEffectPendingEventType && isOpen && (!isMouseEvent || (isMouseEvent && !isMouseDown)); // Effect isn't played when still waiting for a mouseUp if (canPlayEffect) { // Clear pending state var isActivate:Boolean = softKeyboardEffectPendingEventType == SoftKeyboardEvent.SOFT_KEYBOARD_ACTIVATE; // Snap the position only for pending activate events updateSoftKeyboardEffect(isActivate); } } /** * @private */ private function softKeyboardEffectCleanup(event:EffectEvent):void { // Cleanup effect softKeyboardEffect.removeEventListener(EffectEvent.EFFECT_END, softKeyboardEffectCleanup); softKeyboardEffect.removeEventListener(EffectEvent.EFFECT_STOP, softKeyboardEffectCleanup); softKeyboardEffect = null; if (isSoftKeyboardEffectActive) { installActiveResizeListener(); } else { // Remove resize listeners uninstallActiveResizeListener(); // If the deactivate effect is complete, uninstall listeners for // mouse delay and orientation change listeners uninstallSoftKeyboardStateChangeListeners(); } } /** * @private * Set flags when explicit size changes are detected. */ private function resizeHandler(event:Event=null):void { setSoftKeyboardEffectExplicitWidthFlag(!isNaN(explicitWidth)); setSoftKeyboardEffectExplicitHeightFlag(!isNaN(explicitHeight)); } /** * @private * Update effect immediately after orientation change is * followed by a system manager resize. */ private function systemManager_resizeHandler(event:Event):void { // Guard against extraneous resizing during orientation changing. // See SDK-31860. if (!softKeyboardEffectOrientationChanging) updateSoftKeyboardEffect(true); } //-------------------------------------------------------------------------- // // Private Methods // //-------------------------------------------------------------------------- /** * @private * Update the position and height for this pop-up for various state changes * including: soft keyboard activation and deactivation, delayed soft * keyboard events due to mouse and keyboard event sequence, and * orientation change events. */ private function updateSoftKeyboardEffect(snapPosition:Boolean):void { // Stop the current effect if (softKeyboardEffect && softKeyboardEffect.isPlaying) softKeyboardEffect.stop(); // Uninstall resize listeners during the effect. Listeners are // installed again after the effect is complete or immediately affter // the size and position are snapped. uninstallActiveResizeListener(); var softKeyboardRect:Rectangle = systemManager.stage.softKeyboardRect; var isKeyboardOpen:Boolean = isOpen && (softKeyboardRect.height > 0); // Capture start values if not set if (isNaN(softKeyboardEffectCachedYPosition)) { if (isKeyboardOpen) { // Save original y-position and height softKeyboardEffectCachedYPosition = this.y; setSoftKeyboardEffectCachedHeight(this.height); // Initialize explicit size flags resizeHandler(); } else { // Keyboard is closed and we don't have any start values yet. // Nothing to do. return; } } var sandboxRoot:DisplayObject = systemManager.getSandboxRoot(); var yToLocal:Number = softKeyboardEffectCachedYPosition; var heightToLocal:Number = softKeyboardEffectCachedHeight; // If the keyboard is active, check for overlap if (isKeyboardOpen) { var scaleFactor:Number = 1; if (systemManager as SystemManager) scaleFactor = SystemManager(systemManager).densityScale; // All calculations are done in stage coordinates and converted back to // application coordinates when playing effects. Also note that // softKeyboardRect is also in stage coordinates. var popUpY:Number = yToLocal * scaleFactor; var popUpHeight:Number = heightToLocal * scaleFactor; var overlapGlobal:Number = (popUpY + popUpHeight) - softKeyboardRect.y; var yToGlobal:Number = popUpY; var heightToGlobal:Number = popUpHeight; if (overlapGlobal > 0) { // shift y-position up to remove offset overlap if (moveForSoftKeyboard) yToGlobal = Math.max((softKeyboardEffectMarginTop * scaleFactor), (popUpY - overlapGlobal)); // adjust height based on new y-position if (resizeForSoftKeyboard) { // compute new overlap overlapGlobal = (yToGlobal + popUpHeight) - softKeyboardRect.y; // adjust height if there is overlap if (overlapGlobal > 0) heightToGlobal = popUpHeight - overlapGlobal - (softKeyboardEffectMarginBottom * scaleFactor); } } if ((yToGlobal != popUpY) || (heightToGlobal != popUpHeight)) { // convert to application coordinates, move to pixel boundaries yToLocal = Math.floor(yToGlobal / scaleFactor); heightToLocal = Math.floor(heightToGlobal / scaleFactor); // preserve minimum height heightToLocal = Math.max(heightToLocal, getMinBoundsHeight()); } } // Update state _isSoftKeyboardEffectActive = isKeyboardOpen; var duration:Number = getStyle("softKeyboardEffectDuration"); // Only create an effect when not snapping and the duration is // non-negative. An effect will not be created by default if there is // no change. if (!snapPosition && (duration > 0)) softKeyboardEffect = createSoftKeyboardEffect(yToLocal, heightToLocal); if (softKeyboardEffect) { softKeyboardEffect.duration = duration; // Wait a frame so that any queued work can be completed by the framework // and runtime before the effect starts. addEventListener(Event.ENTER_FRAME, startEffect); } else { // No effect, snap. Set position and size explicitly. this.y = yToLocal; if (isOpen) { this.height = heightToLocal; // Validate so that other listeners like Scroller get the // updated dimensions. validateNow(); if (isSoftKeyboardEffectActive) { installActiveResizeListener(); installSoftKeyboardStateChangeListeners(); } } else // if (!isOpen) { // Uninstall mouse delay and orientation change listeners uninstallSoftKeyboardStateChangeListeners(); // Clear explicit size leftover from Resize softKeyboardEffectResetExplicitSize(); // Clear start values softKeyboardEffectCachedYPosition = NaN; setSoftKeyboardEffectExplicitWidthFlag(false); setSoftKeyboardEffectExplicitHeightFlag(false); setSoftKeyboardEffectCachedHeight(NaN); } } } /** * @private * Start waiting for a (mouseDown, mouseUp) event sequence before effect * plays. */ private function setPendingSoftKeyboardEvent(event:SoftKeyboardEvent):void { softKeyboardEffectPendingEventType = event.type; // If the mouseDown event was already received, wait indefinitely // for mouseUp. If the soft keyboard event fired before mouseDown, // start a timer. if (!isMouseDown) { softKeyboardEffectPendingEventTimer = new Timer(softKeyboardEffectPendingEffectDelay, 1); softKeyboardEffectPendingEventTimer.addEventListener(TimerEvent.TIMER_COMPLETE, pendingEffectTimer_timerCompleteHandler); softKeyboardEffectPendingEventTimer.start(); } } /** * @private * Listeners installed just before the soft keyboard effect makes changes * to the original position and height. */ private function installSoftKeyboardStateChangeListeners():void { if (!softKeyboardStateChangeListenersInstalled) { // Listen for mouseDown event on the pop-up and delay the soft // keyboard effect until mouseUp. This allows button click // events to complete normally before the button is re-positioned. // See SDK-31534. var sandboxRoot:DisplayObject = systemManager.getSandboxRoot(); addEventListener(MouseEvent.MOUSE_DOWN, mouseHandler); // Listen for mouseUp events anywhere to play the effect. See SDK-31534. sandboxRoot.addEventListener(MouseEvent.MOUSE_UP, mouseHandler, true /* useCapture */); sandboxRoot.addEventListener(SandboxMouseEvent.MOUSE_UP_SOMEWHERE, systemManager_mouseUpHandler); // Listen for orientationChanging and orientationChange to suspend // pop-up changes until the orientation change is complete. // On iOS, the keyboard is deactivated after orientationChanging, // then immediately activated after orientationChange is complete. // On Android, the keyboard is deactivated after orientationChanging, // but not reactivated after orienationChange. // On QNX, the keyboard is not deactivated at all. The keyboard is // simply activated again after orientationChange. softKeyboardEffectOrientationChanging = false; var smStage:Stage = systemManager.stage; smStage.addEventListener("orientationChanging", stage_orientationChangingHandler); smStage.addEventListener("orientationChange", stage_orientationChangeHandler); softKeyboardStateChangeListenersInstalled = true; } } /** * @private */ private function uninstallSoftKeyboardStateChangeListeners():void { if (softKeyboardStateChangeListenersInstalled) { // Uninstall effect delay handling removeEventListener(MouseEvent.MOUSE_DOWN, mouseHandler); var sandboxRoot:DisplayObject = systemManager.getSandboxRoot(); sandboxRoot.removeEventListener(MouseEvent.MOUSE_UP, mouseHandler, true /* useCapture */); sandboxRoot.removeEventListener(SandboxMouseEvent.MOUSE_UP_SOMEWHERE, systemManager_mouseUpHandler); // Uninstall orientation change handling var smStage:Stage = systemManager.stage; smStage.removeEventListener("orientationChange", stage_orientationChangeHandler); smStage.removeEventListener("orientationChanging", stage_orientationChangeHandler); softKeyboardStateChangeListenersInstalled = false; } } /** * @private * Listeners installed during the phase after the initial activate effect * is complete and before the deactive effect starts. */ private function installActiveResizeListener():void { if (!resizeListenerInstalled) { // Flag size changes while the soft keyboard effect is active (but // not playing) addEventListener("explicitWidthChanged", resizeHandler); addEventListener("explicitHeightChanged", resizeHandler); resizeListenerInstalled = true; } } /** * @private */ private function uninstallActiveResizeListener():void { if (resizeListenerInstalled) { // Uninstall resize listener removeEventListener("explicitWidthChanged", resizeHandler); removeEventListener("explicitHeightChanged", resizeHandler); resizeListenerInstalled = false; } } /** * @private * Clear explicit size that remains after a Resize effect. */ mx_internal function softKeyboardEffectResetExplicitSize():void { // Only remove and restore listeners if they are currently installed. var installed:Boolean = resizeListenerInstalled; if (installed) uninstallActiveResizeListener(); // Remove explicit settings if due to Resize effect if (!softKeyboardEffectExplicitWidthFlag) explicitWidth = NaN; if (!softKeyboardEffectExplicitHeightFlag) explicitHeight = NaN; if (installed) installActiveResizeListener(); } /** * @private */ private function stage_resizeHandler( event:Event ):void { updatePopUpPosition(); } } }