All Downloads are FREE. Search and download functionalities are using the official Maven repository.

scaffold.libs_as.feathers.controls.supportClasses.BaseScreenNavigator.as Maven / Gradle / Ivy

/*
Feathers
Copyright 2012-2015 Bowler Hat LLC. All Rights Reserved.

This program is free software. You can redistribute and/or modify it in
accordance with the terms of the accompanying license agreement.
*/
package feathers.controls.supportClasses
{
	import feathers.controls.IScreen;
	import feathers.core.FeathersControl;
	import feathers.core.IValidating;
	import feathers.events.FeathersEventType;

	import flash.errors.IllegalOperationError;
	import flash.geom.Rectangle;
	import flash.utils.getDefinitionByName;

	import starling.display.DisplayObject;
	import starling.display.Quad;
	import starling.errors.AbstractMethodError;
	import starling.events.Event;

	/**
	 * Dispatched when the active screen changes.
	 *
	 * 

The properties of the event object have the following values:

* * * * * * *
PropertyValue
bubblesfalse
currentTargetThe Object that defines the * event listener that handles the event. For example, if you use * myButton.addEventListener() to register an event listener, * myButton is the value of the currentTarget.
datanull
targetThe Object that dispatched the event; * it is not always the Object listening for the event. Use the * currentTarget property to always access the Object * listening for the event.
* * @eventType starling.events.Event.CHANGE */ [Event(name="change",type="starling.events.Event")] /** * Dispatched when the current screen is removed and there is no active * screen. * *

The properties of the event object have the following values:

* * * * * * *
PropertyValue
bubblesfalse
currentTargetThe Object that defines the * event listener that handles the event. For example, if you use * myButton.addEventListener() to register an event listener, * myButton is the value of the currentTarget.
datanull
targetThe Object that dispatched the event; * it is not always the Object listening for the event. Use the * currentTarget property to always access the Object * listening for the event.
* * @eventType feathers.events.FeathersEventType.CLEAR */ [Event(name="clear",type="starling.events.Event")] /** * Dispatched when the transition between screens begins. * *

The properties of the event object have the following values:

* * * * * * *
PropertyValue
bubblesfalse
currentTargetThe Object that defines the * event listener that handles the event. For example, if you use * myButton.addEventListener() to register an event listener, * myButton is the value of the currentTarget.
datanull
targetThe Object that dispatched the event; * it is not always the Object listening for the event. Use the * currentTarget property to always access the Object * listening for the event.
* * @eventType feathers.events.FeathersEventType.TRANSITION_START */ [Event(name="transitionStart",type="starling.events.Event")] /** * Dispatched when the transition between screens has completed. * *

The properties of the event object have the following values:

* * * * * * *
PropertyValue
bubblesfalse
currentTargetThe Object that defines the * event listener that handles the event. For example, if you use * myButton.addEventListener() to register an event listener, * myButton is the value of the currentTarget.
datanull
targetThe Object that dispatched the event; * it is not always the Object listening for the event. Use the * currentTarget property to always access the Object * listening for the event.
* * @eventType feathers.events.FeathersEventType.TRANSITION_COMPLETE */ [Event(name="transitionComplete",type="starling.events.Event")] /** * A base class for screen navigator components that isn't meant to be * instantiated directly. It should only be subclassed. * * @see feathers.controls.StackScreenNavigator * @see feathers.controls.ScreenNavigator */ public class BaseScreenNavigator extends FeathersControl { /** * @private */ protected static var SIGNAL_TYPE:Class; /** * The screen navigator will auto size itself to fill the entire stage. * * @see #autoSizeMode */ public static const AUTO_SIZE_MODE_STAGE:String = "stage"; /** * The screen navigator will auto size itself to fit its content. * * @see #autoSizeMode */ public static const AUTO_SIZE_MODE_CONTENT:String = "content"; /** * The default transition function. */ protected static function defaultTransition(oldScreen:DisplayObject, newScreen:DisplayObject, completeCallback:Function):void { //in short, do nothing completeCallback(); } /** * Constructor. */ public function BaseScreenNavigator() { super(); if(Object(this).constructor == BaseScreenNavigator) { throw new Error(FeathersControl.ABSTRACT_CLASS_ERROR); } if(!SIGNAL_TYPE) { try { SIGNAL_TYPE = Class(getDefinitionByName("org.osflash.signals.ISignal")); } catch(error:Error) { //signals not being used } } this.addEventListener(Event.ADDED_TO_STAGE, screenNavigator_addedToStageHandler); this.addEventListener(Event.REMOVED_FROM_STAGE, screenNavigator_removedFromStageHandler); } /** * @private */ protected var _activeScreenID:String; /** * The string identifier for the currently active screen. */ public function get activeScreenID():String { return this._activeScreenID; } /** * @private */ protected var _activeScreen:DisplayObject; /** * A reference to the currently active screen. */ public function get activeScreen():DisplayObject { return this._activeScreen; } /** * @private */ protected var _screens:Object = {}; /** * @private */ protected var _previousScreenInTransitionID:String; /** * @private */ protected var _previousScreenInTransition:DisplayObject; /** * @private */ protected var _nextScreenID:String = null; /** * @private */ protected var _nextScreenTransition:Function = null; /** * @private */ protected var _clearAfterTransition:Boolean = false; /** * @private */ protected var _clipContent:Boolean = false; /** * Determines if the navigator's content should be clipped to the width * and height. * *

In the following example, clipping is enabled:

* * * navigator.clipContent = true; * * @default false */ public function get clipContent():Boolean { return this._clipContent; } /** * @private */ public function set clipContent(value:Boolean):void { if(this._clipContent == value) { return; } this._clipContent = value; if(!value) { this.mask = null; } this.invalidate(INVALIDATION_FLAG_STYLES); } /** * @private */ protected var _autoSizeMode:String = AUTO_SIZE_MODE_STAGE; [Inspectable(type="String",enumeration="stage,content")] /** * Determines how the screen navigator will set its own size when its * dimensions (width and height) aren't set explicitly. * *

In the following example, the screen navigator will be sized to * match its content:

* * * navigator.autoSizeMode = ScreenNavigator.AUTO_SIZE_MODE_CONTENT; * * @default ScreenNavigator.AUTO_SIZE_MODE_STAGE * * @see #AUTO_SIZE_MODE_STAGE * @see #AUTO_SIZE_MODE_CONTENT */ public function get autoSizeMode():String { return this._autoSizeMode; } /** * @private */ public function set autoSizeMode(value:String):void { if(this._autoSizeMode == value) { return; } this._autoSizeMode = value; if(this._activeScreen) { if(this._autoSizeMode == AUTO_SIZE_MODE_CONTENT) { this._activeScreen.addEventListener(Event.RESIZE, activeScreen_resizeHandler); } else { this._activeScreen.removeEventListener(Event.RESIZE, activeScreen_resizeHandler); } } this.invalidate(INVALIDATION_FLAG_SIZE); } /** * @private */ protected var _waitingTransition:Function; /** * @private */ private var _waitingForTransitionFrameCount:int = 1; /** * @private */ protected var _isTransitionActive:Boolean = false; /** * Indicates whether the screen navigator is currently transitioning * between screens. */ public function get isTransitionActive():Boolean { return this._isTransitionActive; } /** * @private */ override public function dispose():void { if(this._activeScreen) { this.cleanupActiveScreen(); this._activeScreen = null; this._activeScreenID = null; } super.dispose(); } /** * Removes all screens that were added with addScreen(). * * @see #addScreen() */ public function removeAllScreens():void { if(this._isTransitionActive) { throw new IllegalOperationError("Cannot remove all screens while a transition is active."); } if(this._activeScreen) { //if someone meant to have a transition, they would have called //clearScreen() this.clearScreenInternal(null); this.dispatchEventWith(FeathersEventType.CLEAR); } for(var id:String in this._screens) { delete this._screens[id]; } } /** * Determines if the specified screen identifier has been added with * addScreen(). * * @see #addScreen() */ public function hasScreen(id:String):Boolean { return this._screens.hasOwnProperty(id); } /** * Returns a list of the screen identifiers that have been added. */ public function getScreenIDs(result:Vector. = null):Vector. { if(result) { result.length = 0; } else { result = new []; } var pushIndex:int = 0; for(var id:String in this._screens) { result[pushIndex] = id; pushIndex++; } return result; } /** * @private */ override protected function draw():void { var sizeInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_SIZE); var selectionInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_SELECTED); var stylesInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_STYLES); sizeInvalid = this.autoSizeIfNeeded() || sizeInvalid; if(sizeInvalid || selectionInvalid) { if(this._activeScreen) { if(this._activeScreen.width != this.actualWidth) { this._activeScreen.width = this.actualWidth; } if(this._activeScreen.height != this.actualHeight) { this._activeScreen.height = this.actualHeight; } } } if(stylesInvalid || sizeInvalid) { this.refreshMask(); } } /** * If the component's dimensions have not been set explicitly, it will * measure its content and determine an ideal size for itself. If the * explicitWidth or explicitHeight member * variables are set, those value will be used without additional * measurement. If one is set, but not the other, the dimension with the * explicit value will not be measured, but the other non-explicit * dimension will still need measurement. * *

Calls setSizeInternal() to set up the * actualWidth and actualHeight member * variables used for layout.

* *

Meant for internal use, and subclasses may override this function * with a custom implementation.

*/ protected function autoSizeIfNeeded():Boolean { var needsWidth:Boolean = this._explicitWidth !== this._explicitWidth; //isNaN var needsHeight:Boolean = this._explicitHeight !== this._explicitHeight; //isNaN if(!needsWidth && !needsHeight) { return false; } if((this._autoSizeMode == AUTO_SIZE_MODE_CONTENT || !this.stage) && this._activeScreen is IValidating) { IValidating(this._activeScreen).validate(); } var newWidth:Number = this._explicitWidth; if(needsWidth) { if(this._autoSizeMode == AUTO_SIZE_MODE_CONTENT || !this.stage) { newWidth = this._activeScreen ? this._activeScreen.width : 0; } else { newWidth = this.stage.stageWidth; } } var newHeight:Number = this._explicitHeight; if(needsHeight) { if(this._autoSizeMode == AUTO_SIZE_MODE_CONTENT || !this.stage) { newHeight = this._activeScreen ? this._activeScreen.height : 0; } else { newHeight = this.stage.stageHeight; } } return this.setSizeInternal(newWidth, newHeight, false); } /** * @private */ protected function addScreenInternal(id:String, item:IScreenNavigatorItem):void { if(this._screens.hasOwnProperty(id)) { throw new ArgumentError("Screen with id '" + id + "' already defined. Cannot add two screens with the same id."); } this._screens[id] = item; } /** * @private */ protected function refreshMask():void { if(!this._clipContent) { return; } var mask:DisplayObject = this.mask as Quad; if(mask) { mask.width = this.actualWidth; mask.height = this.actualHeight; } else { mask = new Quad(1, 1, 0xff00ff); //the initial dimensions cannot be 0 or there's a runtime error, //and these values might be 0 mask.width = this.actualWidth; mask.height = this.actualHeight; this.mask = mask; } } /** * @private */ protected function removeScreenInternal(id:String):IScreenNavigatorItem { if(!this._screens.hasOwnProperty(id)) { throw new ArgumentError("Screen '" + id + "' cannot be removed because it has not been added."); } if(this._isTransitionActive && (id == this._previousScreenInTransitionID || id == this._activeScreenID)) { throw new IllegalOperationError("Cannot remove a screen while it is transitioning in or out.") } if(this._activeScreenID == id) { //if someone meant to have a transition, they would have called //clearScreen() this.clearScreenInternal(null); this.dispatchEventWith(FeathersEventType.CLEAR); } var item:IScreenNavigatorItem = IScreenNavigatorItem(this._screens[id]); delete this._screens[id]; return item; } /** * @private */ protected function showScreenInternal(id:String, transition:Function, properties:Object = null):DisplayObject { if(!this.hasScreen(id)) { throw new ArgumentError("Screen with id '" + id + "' cannot be shown because it has not been defined."); } if(this._isTransitionActive) { this._nextScreenID = id; this._nextScreenTransition = transition; this._clearAfterTransition = false; return null; } if(this._activeScreenID == id) { return this._activeScreen; } this._previousScreenInTransition = this._activeScreen; this._previousScreenInTransitionID = this._activeScreenID; if(this._activeScreen) { this.cleanupActiveScreen(); } this._isTransitionActive = true; var item:IScreenNavigatorItem = IScreenNavigatorItem(this._screens[id]); this._activeScreen = item.getScreen(); this._activeScreenID = id; for(var propertyName:String in properties) { this._activeScreen[propertyName] = properties[propertyName]; } if(this._activeScreen is IScreen) { var screen:IScreen = IScreen(this._activeScreen); screen.screenID = this._activeScreenID; screen.owner = this; //subclasses will implement the interface } if(this._autoSizeMode == AUTO_SIZE_MODE_CONTENT || !this.stage) { this._activeScreen.addEventListener(Event.RESIZE, activeScreen_resizeHandler); } this.prepareActiveScreen(); this.addChild(this._activeScreen); this.invalidate(INVALIDATION_FLAG_SELECTED); if(this._validationQueue && !this._validationQueue.isValidating) { //force a COMPLETE validation of everything //but only if we're not already doing that... this._validationQueue.advanceTime(0); } else if(!this._isValidating) { this.validate(); } this.dispatchEventWith(FeathersEventType.TRANSITION_START); this._activeScreen.dispatchEventWith(FeathersEventType.TRANSITION_IN_START); if(this._previousScreenInTransition) { this._previousScreenInTransition.dispatchEventWith(FeathersEventType.TRANSITION_OUT_START); } if(transition != null) { //temporarily make the active screen invisible because the //transition doesn't start right away. this._activeScreen.visible = false; this._waitingForTransitionFrameCount = 0; this._waitingTransition = transition; //this is a workaround for an issue with transition performance. //see the comment in the listener for details. this.addEventListener(Event.ENTER_FRAME, waitingForTransition_enterFrameHandler); } else { defaultTransition(this._previousScreenInTransition, this._activeScreen, transitionComplete); } this.dispatchEventWith(Event.CHANGE); return this._activeScreen; } /** * @private */ protected function clearScreenInternal(transition:Function = null):void { if(!this._activeScreen) { //no screen visible. return; } if(this._isTransitionActive) { this._nextScreenID = null; this._clearAfterTransition = true; this._nextScreenTransition = transition; return; } this.cleanupActiveScreen(); this._isTransitionActive = true; this._previousScreenInTransition = this._activeScreen; this._previousScreenInTransitionID = this._activeScreenID; this._activeScreen = null; this._activeScreenID = null; this.dispatchEventWith(FeathersEventType.TRANSITION_START); this._previousScreenInTransition.dispatchEventWith(FeathersEventType.TRANSITION_OUT_START); if(transition !== null) { this._waitingForTransitionFrameCount = 0; this._waitingTransition = transition; //this is a workaround for an issue with transition performance. //see the comment in the listener for details. this.addEventListener(Event.ENTER_FRAME, waitingForTransition_enterFrameHandler); } else { defaultTransition(this._previousScreenInTransition, this._activeScreen, transitionComplete); } this.invalidate(INVALIDATION_FLAG_SELECTED); } /** * @private */ protected function prepareActiveScreen():void { throw new AbstractMethodError(); } /** * @private */ protected function cleanupActiveScreen():void { throw new AbstractMethodError(); } /** * @private */ protected function transitionComplete(cancelTransition:Boolean = false):void { //consider the transition still active if something is already //queued up to happen next. if an event listener asks to show a new //screen, it needs to replace what is queued up. this._isTransitionActive = this._clearAfterTransition || this._nextScreenID; if(cancelTransition) { if(this._activeScreen) { var item:IScreenNavigatorItem = IScreenNavigatorItem(this._screens[this._activeScreenID]); this.cleanupActiveScreen(); this.removeChild(this._activeScreen, item.canDispose); } this._activeScreen = this._previousScreenInTransition; this._activeScreenID = this._previousScreenInTransitionID; this._previousScreenInTransition = null; this._previousScreenInTransitionID = null; this.prepareActiveScreen(); this.dispatchEventWith(FeathersEventType.TRANSITION_CANCEL); } else { //we need to save these in local variables because a new //transition may be started in the listeners for the transition //complete events, and that will overwrite them. var activeScreen:DisplayObject = this._activeScreen; var previousScreen:DisplayObject = this._previousScreenInTransition; var previousScreenID:String = this._previousScreenInTransitionID; item = IScreenNavigatorItem(this._screens[previousScreenID]); this._previousScreenInTransition = null; this._previousScreenInTransitionID = null; if(previousScreen) { previousScreen.dispatchEventWith(FeathersEventType.TRANSITION_OUT_COMPLETE) } if(activeScreen) { activeScreen.dispatchEventWith(FeathersEventType.TRANSITION_IN_COMPLETE) } //we need to dispatch this event before the previous screen's //owner property is set to null because legacy code that was //written before TRANSITION_OUT_COMPLETE existed may be using //this event for the same purpose. this.dispatchEventWith(FeathersEventType.TRANSITION_COMPLETE); if(previousScreen) { if(previousScreen is IScreen) { var screen:IScreen = IScreen(previousScreen); screen.screenID = null; screen.owner = null; } previousScreen.removeEventListener(Event.RESIZE, activeScreen_resizeHandler); this.removeChild(previousScreen, item.canDispose); } } this._isTransitionActive = false; if(this._clearAfterTransition) { this.clearScreenInternal(this._nextScreenTransition); } else if(this._nextScreenID) { this.showScreenInternal(this._nextScreenID, this._nextScreenTransition); } this._nextScreenID = null; this._nextScreenTransition = null; this._clearAfterTransition = false; } /** * @private */ protected function screenNavigator_addedToStageHandler(event:Event):void { this.stage.addEventListener(Event.RESIZE, stage_resizeHandler); } /** * @private */ protected function screenNavigator_removedFromStageHandler(event:Event):void { this.stage.removeEventListener(Event.RESIZE, stage_resizeHandler); } /** * @private */ protected function activeScreen_resizeHandler(event:Event):void { if(this._isValidating || this._autoSizeMode != AUTO_SIZE_MODE_CONTENT) { return; } this.invalidate(INVALIDATION_FLAG_SIZE); } /** * @private */ protected function stage_resizeHandler(event:Event):void { this.invalidate(INVALIDATION_FLAG_SIZE); } /** * @private */ private function waitingForTransition_enterFrameHandler(event:Event):void { //we need to wait a couple of frames before we can start the //transition to make it as smooth as possible. this feels a little //hacky, to be honest, but I can't figure out why waiting only one //frame won't do the trick. the delay is so small though that it's //virtually impossible to notice. if(this._waitingForTransitionFrameCount < 2) { this._waitingForTransitionFrameCount++; return; } this.removeEventListener(Event.ENTER_FRAME, waitingForTransition_enterFrameHandler); if(this._activeScreen) { this._activeScreen.visible = true; } var transition:Function = this._waitingTransition; this._waitingTransition = null; transition(this._previousScreenInTransition, this._activeScreen, transitionComplete); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy