| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // Licensed to the Apache Software Foundation (ASF) under one or more |
| // contributor license agreements. See the NOTICE file distributed with |
| // this work for additional information regarding copyright ownership. |
| // The ASF licenses this file to You under the Apache License, Version 2.0 |
| // (the "License"); you may not use this file except in compliance with |
| // the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| package spark.components |
| { |
| import flash.events.Event; |
| |
| import mx.core.IUIComponent; |
| import mx.core.IVisualElement; |
| import mx.events.FlexEvent; |
| import mx.states.State; |
| |
| import spark.components.supportClasses.SkinnableComponent; |
| |
| [SkinState("rotatingState")] |
| [SkinState("notRotatingState")] |
| |
| //-------------------------------------- |
| // Styles |
| //-------------------------------------- |
| |
| /** |
| * The interval to delay, in milliseconds, between rotations of this |
| * component. Controls the speed at which this component spins. |
| * |
| * @default 50 |
| * |
| * @langversion 3.0 |
| * @playerversion AIR 2.5 |
| * @productversion Flex 4.5 |
| * |
| */ |
| [Style(name="rotationInterval", type="Number", format="Time", inherit="no")] |
| |
| /** |
| * Color of the spokes of the spinner. |
| * |
| * @default 0x000000 |
| * |
| * @langversion 3.0 |
| * @playerversion AIR 2.5 |
| * @productversion Flex 4.5 |
| */ |
| [Style(name="symbolColor", type="uint", format="Color", inherit="yes", theme="spark,mobile")] |
| |
| //-------------------------------------- |
| // Other metadata |
| //-------------------------------------- |
| |
| [IconFile("BusyIndicator.png")] |
| |
| /** |
| * The BusyIndicator defines a component to display when a long-running |
| * operation is in progress. This component creates a spinner with twelve spokes. |
| * The color of the spokes is controlled by the value of the <code>symbolColor</code> style. |
| * The transparency of this component can be modified using the <code>alpha</code> property, |
| * but the alpha value of each spoke cannot be modified. |
| * |
| * <p>The following image shows the BusyIndicator at the bottom of the screen next |
| * to the Submit button:</p> |
| * |
| * <p> |
| * <img src="../../images/bi_busy_indicator_bi.png" alt="Busy indicator" /> |
| * </p> |
| * |
| * <p>The speed at which this component spins is controlled by the <code>rotationInterval</code> |
| * style. The <code>rotationInterval</code> style sets the delay, in milliseconds, between |
| * rotations. Decrease the <code>rotationInterval</code> value to increase the speed of the spin.</p> |
| * |
| * <p>The BusyIndicator has the following default characteristics:</p> |
| * <table class="innertable"> |
| * <tr><th>Characteristic</th><th>Description</th></tr> |
| * <tr><td>Default size</td><td>160 DPI: 26x26 pixels<br> |
| * 240 DPI: 40x40 pixels<br> |
| * 320 DPI: 52x52 pixels<br> |
| * 380 DPI: 80x80 pixels<br></td></tr> |
| * <tr><td>Minimum size</td><td>20x20 pixels</td></tr> |
| * <tr><td>Maximum size</td><td>No limit</td></tr> |
| * </table> |
| * |
| * <p>The diameter of the BusyIndicator's spinner is the minimum of the width and |
| * height of the component. The diameter must be an even number, and is |
| * reduced by one if it is set to an odd number.</p> |
| * |
| * @mxml |
| * |
| * <p>The <code><s:BusyIndicator></code> tag inherits all of the tag |
| * attributes of its superclass and adds the following tag attributes:</p> |
| * |
| * <pre> |
| * <s:BusyIndicator |
| * <strong>Common Styles</strong> |
| * rotationInterval=50 |
| * |
| * <strong>Spark Styles</strong> |
| * symbolColor="0x000000" |
| * |
| * <strong>Mobile Styles</strong> |
| * symbolColor="0x000000" |
| * > |
| * </pre> |
| * |
| * @includeExample examples/BusyIndicatorExample.mxml -noswf |
| * @includeExample examples/views/BusyIndicatorExampleHomeView.mxml -noswf |
| * |
| * @langversion 3.0 |
| * @playerversion AIR 2.5 |
| * @productversion Flex 4.5 |
| */ |
| public class BusyIndicator extends SkinnableComponent |
| { |
| private var effectiveVisibility:Boolean = false; |
| private var effectiveVisibilityChanged:Boolean = true; |
| |
| public function BusyIndicator() |
| { |
| super(); |
| // Listen to added to stage and removed from stage. |
| // Start rotating when we are on the stage and stop |
| // when we are removed from the stage. |
| addEventListener(Event.ADDED_TO_STAGE, addedToStageHandler); |
| addEventListener(Event.REMOVED_FROM_STAGE, removedFromStageHandler); |
| states = [ |
| new State({name:"notRotatingState"}), |
| new State({name:"rotatingState"}) |
| ]; |
| } |
| |
| override protected function getCurrentSkinState():String |
| { |
| return currentState; |
| } |
| |
| private function addedToStageHandler(event:Event):void |
| { |
| // Check our visibility here since we haven't added |
| // visibility listeners yet. |
| computeEffectiveVisibility(); |
| |
| if (canRotate()) |
| currentState = "rotatingState"; |
| |
| addVisibilityListeners(); |
| invalidateSkinState(); |
| } |
| |
| private function removedFromStageHandler(event:Event):void |
| { |
| currentState = "notRotatingState"; |
| |
| removeVisibilityListeners(); |
| invalidateSkinState(); |
| } |
| |
| private function computeEffectiveVisibility():void |
| { |
| |
| // Check our design layer first. |
| if (designLayer && !designLayer.effectiveVisibility) |
| { |
| effectiveVisibility = false; |
| return; |
| } |
| |
| // Start out with true visibility and enablement |
| // then loop up parent-chain to see if any of them are false. |
| effectiveVisibility = true; |
| var current:IVisualElement = this; |
| |
| while (current) |
| { |
| if (!current.visible) |
| { |
| if (!(current is IUIComponent) || !IUIComponent(current).isPopUp) |
| { |
| // Treat all pop ups as if they were visible. This is to |
| // fix a bug where the BusyIndicator does not spin when it |
| // is inside modal popup. The problem is in we do not get |
| // an event when the modal window is made visible in |
| // PopUpManagerImpl.fadeInEffectEndHandler(). When the modal |
| // window is made visible, setVisible() is passed "true" so |
| // as to not send an event. When do get events when the |
| // non-modal windows are popped up. Only modal windows are |
| // a problem. |
| // The downside of this fix is BusyIndicator components that are |
| // inside of hidden, non-modal, popup windows will paint themselves |
| // on a timer. |
| effectiveVisibility = false; |
| break; |
| } |
| } |
| |
| current = current.parent as IVisualElement; |
| } |
| } |
| |
| /** |
| * The BusyIndicator can be rotated if it is both on the display list and |
| * visible. |
| * |
| * @returns true if the BusyIndicator can be rotated, false otherwise. |
| */ |
| private function canRotate():Boolean |
| { |
| if (effectiveVisibility && stage != null) |
| return true; |
| |
| return false; |
| } |
| |
| |
| /** |
| * @private |
| * Add event listeners for SHOW and HIDE on all the ancestors up the parent chain. |
| * Adding weak event listeners just to be safe. |
| */ |
| private function addVisibilityListeners():void |
| { |
| var current:IVisualElement = this.parent as IVisualElement; |
| while (current) |
| { |
| // add visibility listeners to the parent |
| current.addEventListener(FlexEvent.HIDE, visibilityChangedHandler, false, 0, true); |
| current.addEventListener(FlexEvent.SHOW, visibilityChangedHandler, false, 0, true); |
| |
| current = current.parent as IVisualElement; |
| } |
| } |
| |
| /** |
| * @private |
| * Remove event listeners for SHOW and HIDE on all the ancestors up the parent chain. |
| */ |
| private function removeVisibilityListeners():void |
| { |
| var current:IVisualElement = this; |
| while (current) |
| { |
| current.removeEventListener(FlexEvent.HIDE, visibilityChangedHandler, false); |
| current.removeEventListener(FlexEvent.SHOW, visibilityChangedHandler, false); |
| |
| current = current.parent as IVisualElement; |
| } |
| } |
| |
| /** |
| * @private |
| * Event call back whenever the visibility of us or one of our ancestors |
| * changes |
| */ |
| private function visibilityChangedHandler(event:FlexEvent):void |
| { |
| effectiveVisibilityChanged = true; |
| invalidateProperties(); |
| } |
| |
| |
| } |
| } |
| |