import { DisplayObject } from './DisplayObject';
import { SoundTransform } from '../media/SoundTransform';
import { Timeline, DisplayObjectContainer as AwayDisplayObjectContainer,
DisplayObject as AwayDisplayObject, IDisplayObjectAdapter, FrameScriptManager } from '@awayjs/scene';
import { MovieClip as AwayMovieClip } from '@awayjs/scene';
import { IVirtualSceneGraphItem } from './IVirtualSceneGraphItem';
import { AssetBase, Debug } from '@awayjs/core';
import { InteractiveObject } from './InteractiveObject';
import { constructClassFromSymbol } from '@awayfl/avm2';
/**
* The SimpleButton class lets you control all instances of button symbols in a SWF
* file.
*
*
In Flash Professional, you can give a button an instance name in the
* Property inspector. SimpleButton instance names are displayed in the Movie
* Explorer and in the Insert Target Path dialog box in the Actions
* panel. After you create an instance of a button in Flash Professional, you can
* use the methods and properties of the SimpleButton class to manipulate buttons
* with ActionScript.
In ActionScript 3.0,
* you use the new SimpleButton() constructor to create a
* SimpleButton instance.
The SimpleButton class inherits from the InteractiveObject class.
*
* EXAMPLE:
*
* The following example uses the SimpleButtonExample class, which in
* turn uses the CustomSimpleButton class, and this class then instantiates four
* ButtonDisplayState objects. The result is a button that is created in the shape of
* a square, whose background color changes based on the mouse state by overriding instance properties of
* the SimpleButton class. This is accomplished by performing the following steps:
*
* - In the SimpleButtonExample()
* constructor, a new CustomSimpleButton object of type
* SimpleButton, called button,
* is created, which calls the CustomSimpleButton constructor
* method. The button object is the added to the display list.
* The button's color and size are
* determined in the steps that follow.
-
* In the CustomSimpleButton class, instance properties are declared that are later used
* to control the size and background color of button,
* based on the state it is in (orange
* in the normal state, dark yellow in the mouse over state, an light blue in the mouse down state).
* In all of the button's states,
* the size of the square is set to 80 pixels by using the
* size property.
* - The constructor function for the CustomSimpleButton class sets the
* downState,
* overState,
* upState,
* hitTestState,
* and useHandCursor properties with
* four instances of the ButtonDisplayState class.
* - In the ButtonDisplayState class, the constructor sets the value of the
* square's size and background color and calls the
* draw() method.
* - The draw()
* method redraws the square with the size and background color set in
* the constructor based on the button's state.
*
*/
export class SimpleButton extends InteractiveObject {
private _soundTransform: SoundTransform;
/**
* Creates a new SimpleButton instance. Any or all of the display objects that represent
* the various button states can be set as parameters in the constructor.
* @param upState The initial value for the SimpleButton up state.
* @param overState The initial value for the SimpleButton over state.
* @param downState The initial value for the SimpleButton down state.
* @param hitTestState The initial value for the SimpleButton hitTest state.
*/
constructor (upState: DisplayObject = null,
overState: DisplayObject = null,
downState: DisplayObject = null,
hitTestState: DisplayObject = null) {
super();
( this._adaptee).addButtonListeners();
}
// todo: this methods are also defined on Sprite
// in avm2 SimpleButton extends from InteractiveObject, not from MovieClip/Sprite
public addTimelineChildAtDepth(child: AwayDisplayObject, depth: number): AwayDisplayObject {
child.reset();
const maxIndex = ( this.adaptee).numChildren - 1;
let index = maxIndex + 1;
let scriptChildsOffset = 0;
for (let i = maxIndex; i >= 0; i--) {
const current = ( this.adaptee).getChildAt(i);
if (current._avmDepthID == -1) {
scriptChildsOffset++;
}
if (current._avmDepthID > -1) {
if (current._avmDepthID < depth) {
index = i + 1 + scriptChildsOffset;
break;
}
scriptChildsOffset = 0;
index = i;
}
}
child._avmDepthID = depth;
(child).just_added_to_timeline = true;
( this.adaptee)._sessionID_childs[child._sessionID] = child;
return ( this.adaptee).addChildAt(child, index);
}
public removeTimelineChildAt(value: number): void {
// in as3 we remove by sessionID
const child = ( this.adaptee)._sessionID_childs[value];
if (child) {
delete ( this.adaptee)._sessionID_childs[value];
( this.adaptee).removeChild(child);
}
}
/**
* queue the mc for executing framescripts
* this only queues the frame-index as a number,
* the actual framescripts will be retrieved in MovieClip.executeScripts
* @param timeline
* @param frame_idx
* @param scriptPass1
*/
public queueFrameScripts(timeline: Timeline, frame_idx: number, scriptPass1: boolean) {
//console.log("add framescript", target_mc, target_mc.name, keyframe_idx, scriptPass1 );
if (scriptPass1)
FrameScriptManager.add_script_to_queue( this.adaptee, frame_idx);
else
FrameScriptManager.add_script_to_queue_pass2( this.adaptee, frame_idx);
}
public removeAllTimelineChilds(): void {
const adaptee: AwayMovieClip = this.adaptee;
for (const key in adaptee._sessionID_childs) {
const child = adaptee._sessionID_childs[key];
if (child) {
delete adaptee._sessionID_childs[key];
adaptee.removeChild(child);
}
}
}
public constructFrame(timeline: Timeline, start_construct_idx: number,
target_keyframe_idx: number, jump_forward: boolean,
frame_idx: number, queue_pass2: boolean, queue_script: boolean) {
const adaptee: AwayMovieClip = this.adaptee;
let len = adaptee.numChildren;
let virtualSceneGraph = [];
const existingSessionIDs = {};
for (let i = 0; i < len; i++) {
// collect the existing children into a virtual-scenegraph
const child = adaptee.getChildAt(i);
// if jumping forward, we continue from current frame, so we collect all objects
// if jumping back, we want to only collect script-children. timeline childs are ignored
if (jump_forward || child._sessionID == -1) {
virtualSceneGraph[virtualSceneGraph.length] = {
sessionID:child._sessionID,
as3DepthID:child._avmDepthID,
addedOnTargetFrame:false,
child:child
};
}
if (child._sessionID != -1)
existingSessionIDs[child._sessionID] = child;
}
let i: number;
let k: number;
// step1: apply remove / add commands to virtual scenegraph. collect update commands aswell
timeline._update_indices.length = 0;
timeline._update_frames.length = 0;
let update_cnt = 0;
let start_index: number;
let end_index: number;
for (k = start_construct_idx; k <= target_keyframe_idx; k++) {
let frame_command_idx: number = timeline.frame_command_indices[k];
const frame_recipe: number = timeline.frame_recipe[k];
if (frame_recipe & 2) {
start_index = timeline.command_index_stream[frame_command_idx];
end_index = start_index + timeline.command_length_stream[frame_command_idx++];
const removeSessionIDs = {};
for (i = start_index; i < end_index; i++) {
removeSessionIDs[timeline.remove_child_stream[i]] = true;
}
const newVirtualSceneGraph = [];
len = virtualSceneGraph.length;
for (let i = 0; i < len; i++) {
if (!removeSessionIDs[virtualSceneGraph[i].sessionID])
newVirtualSceneGraph[newVirtualSceneGraph.length] = virtualSceneGraph[i];
}
virtualSceneGraph = newVirtualSceneGraph;
}
if (frame_recipe & 4) {
start_index = timeline.command_index_stream[frame_command_idx];
end_index = start_index + timeline.command_length_stream[frame_command_idx++];
for (i = start_index; i < end_index; i++) {
const maxIndex = virtualSceneGraph.length - 1;
const depth = timeline.add_child_stream[i * 3 + 1];
let index = maxIndex + 1;
let scriptChildsOffset = 0;
for (let i = maxIndex; i >= 0; i--) {
const current = virtualSceneGraph[i];
if (current.as3DepthID == -1) {
scriptChildsOffset++;
}
if (current.as3DepthID > -1) {
if (current.as3DepthID == depth &&
current.sessionID == timeline.add_child_stream[i * 3]) {
index = -1;
break;
}
if (current.as3DepthID < depth) {
index = i + 1 + scriptChildsOffset;
break;
}
scriptChildsOffset = 0;
index = i;
}
}
if (index >= virtualSceneGraph.length) {
virtualSceneGraph[virtualSceneGraph.length] = {
sessionID:timeline.add_child_stream[i * 3],
addedOnTargetFrame:k == target_keyframe_idx,
symbolID:timeline.add_child_stream[i * 3 + 2],
as3DepthID:timeline.add_child_stream[i * 3 + 1],
};
} else if (index > -1) {
virtualSceneGraph.splice(index, 0, {
sessionID:timeline.add_child_stream[i * 3],
addedOnTargetFrame:k == target_keyframe_idx,
symbolID:timeline.add_child_stream[i * 3 + 2],
as3DepthID:timeline.add_child_stream[i * 3 + 1],
});
}
}
}
if (frame_recipe & 8) {
timeline._update_frames[update_cnt] = timeline.keyframe_firstframes[k];
timeline._update_indices[update_cnt++] = frame_command_idx++;// execute update command later
}
if (frame_recipe & 16 && k == target_keyframe_idx) {
timeline.start_sounds(adaptee, frame_command_idx);
}
}
const newChildren: AwayDisplayObject[] = [];
let vsItem: IVirtualSceneGraphItem;
const newChilds: AwayDisplayObject[] = [];
const newChildsOnTargetFrame: AwayDisplayObject[] = [];
// step2: build new list of children from virtual-scenegraph
adaptee._sessionID_childs = {};
len = newChildren.length = virtualSceneGraph.length;
for (let i = 0; i < len; i++) {
vsItem = virtualSceneGraph[i];
if (vsItem.sessionID == -1 && vsItem.child) {
// this must be a script child
newChildren[i] = vsItem.child.adaptee;
} else if (existingSessionIDs[vsItem.sessionID]) {
// the same sessionID already is child of the mc
const existingChild = existingSessionIDs[vsItem.sessionID];
adaptee._sessionID_childs[vsItem.sessionID] = existingChild;
newChildren[i] = existingChild;
//console.log("vsItem.exists", vsItem);
if (!jump_forward) {
if (newChildren[i]._adapter) {
if (!( newChildren[i].adapter).isColorTransformByScript()) {
newChildren[i].transform.clearColorTransform();
}
if (!( newChildren[i].adapter).isBlockedByScript()
&& !(newChildren[i]).noTimelineUpdate) {
newChildren[i].transform.clearMatrix3D();
newChildren[i].updateTimelineMask(null);
}
if (!( newChildren[i].adapter).isVisibilityByScript()) {
newChildren[i].visible = true;
}
} else {
newChildren[i].transform.clearColorTransform();
newChildren[i].transform.clearMatrix3D();
newChildren[i].visible = true;
newChildren[i].updateTimelineMask(null);
}
}
} else {
const newChild = timeline.getChildInstance(vsItem.symbolID, vsItem.sessionID);
if (this.adaptee.isSlice9ScaledMC && newChild.assetType == '[asset Sprite]') {
newChild.isSlice9ScaledSprite = true;
}
newChild._sessionID = vsItem.sessionID;
newChild._avmDepthID = vsItem.as3DepthID;
adaptee._sessionID_childs[vsItem.sessionID] = newChild;
newChildren[i] = newChild;
if (vsItem.addedOnTargetFrame) {
newChildsOnTargetFrame[newChildsOnTargetFrame.length] = newChild;
} else {
newChilds[newChilds.length] = newChild;
}
}
}
// step3: remove children that no longer exists
for (let i = adaptee.numChildren - 1; i >= 0; i--) {
if (newChildren.indexOf(adaptee.getChildAt(i)) < 0) {
adaptee.removeChildAt(i);
}
}
// step4: setup new children that have not been added on new frame (prevent frame-scripts)
for (let i = 0; i < newChildren.length; i++)
if (!adaptee.contains(newChildren[i]))
adaptee.addChildAt(newChildren[i], i);
adaptee.preventScript = true;
this.finalizeChildren(newChilds);
// step5: queue frame-script for new frame
if (queue_script)
this.queueFrameScripts(timeline, frame_idx, !queue_pass2);
// step6: setup children that have been added on new frame (allow frame-scripts)
adaptee.preventScript = true;
this.finalizeChildren(newChildsOnTargetFrame);
}
public finalizeChildren(children: AwayDisplayObject[]) {
const len = children.length;
for (let i = 0; i < len; i++) {
const newChild = children[i];
newChild.reset();
}
}
public initAdapter(): void {}
public registerScriptObject(child: any): void {
}
public unregisterScriptObject(child: any): void {
}
/**
* Specifies a display object that is used as the visual
* object for the button "Down" state βthe state that the button is in when the user
* selects the hitTestState object.
*/
public get downState (): DisplayObject {
// @todo
Debug.throwPIR('playerglobals/display/SimpleButton', 'get downState', '');
return null;
}
public set downState (value: DisplayObject) {
// @todo
Debug.throwPIR('playerglobals/display/SimpleButton', 'set downState', '');
}
/**
* A Boolean value that specifies whether a button is enabled. When a
* button is disabled (the enabled property is set to false),
* the button is visible but cannot be clicked. The default value is
* true. This property is useful if you want to
* disable part of your navigation; for example, you might want to disable a
* button in the currently displayed page so that it can't be clicked and
* the page cannot be reloaded.
*
* Note: To prevent mouseClicks on a button, set both the enabled
* and mouseEnabled properties to false.
*/
public get enabled (): boolean {
// @todo
Debug.throwPIR('playerglobals/display/SimpleButton', 'get enabled', '');
return false;
}
public set enabled (value: boolean) {
// @todo
Debug.throwPIR('playerglobals/display/SimpleButton', 'set enabled', '');
}
/**
* Specifies a display object that is used as the hit testing object for the button. For a basic button, set the
* hitTestState property to the same display object as the overState
* property. If you do not set the hitTestState property, the SimpleButton
* is inactive β it does not respond to user input events.
*/
public get hitTestState (): DisplayObject {
return this.adaptee.pickObject.adapter;
}
public set hitTestState (value: DisplayObject) {
this.adaptee.pickObject = value.adaptee;
}
/**
* Specifies a display object that is used as the visual
* object for the button over state β the state that the button is in when
* the pointer is positioned over the button.
*/
public get overState (): DisplayObject {
// @todo
Debug.throwPIR('playerglobals/display/SimpleButton', 'get overState', '');
return null;
}
public set overState (value: DisplayObject) {
// @todo
Debug.throwPIR('playerglobals/display/SimpleButton', 'set overState', '');
}
/**
* The SoundTransform object assigned to this button. A SoundTransform object
* includes properties for setting volume, panning, left speaker assignment, and right
* speaker assignment. This SoundTransform object applies to all states of the button.
* This SoundTransform object affects only embedded sounds.
* @internal Should information from AS2 setTransform be here? e.g. percentage values indicating
* how much of the left input to play in the left speaker or right speaker; it is generally
* best to use 22-KHZ 6-bit mono sounds?
*/
public get soundTransform(): SoundTransform {
// we not create this in the constructor, because it gets overwritten from Sound in most cases anyway
// but we still need a Soundtransform to be available in the getter
if (!this._soundTransform) {
this._soundTransform = new ( this.sec).flash.media.SoundTransform();
}
this._soundTransform.volume = ( this.adaptee).soundVolume;
return this._soundTransform;
}
public set soundTransform(value: SoundTransform) {
( this.adaptee).soundVolume = value ? value.volume : 1;
this._soundTransform = value;
}
/**
* Indicates whether other display objects that are SimpleButton or MovieClip objects can receive
* user input release events. The trackAsMenu property lets you create menus. You
* can set the trackAsMenu property on any SimpleButton or MovieClip object.
* If the trackAsMenu property does not exist, the default behavior is
* false.
*
* You can change the trackAsMenu property at any time; the
* modified button immediately takes on the new behavior.
*/
public get trackAsMenu (): boolean {
// @todo
Debug.throwPIR('playerglobals/display/SimpleButton', 'get trackAsMenu', '');
return false;
}
public set trackAsMenu (value: boolean) {
// @todo
Debug.throwPIR('playerglobals/display/SimpleButton', 'set trackAsMenu', '');
}
/**
* Specifies a display object that is used as the visual
* object for the button up state β the state that the button is in when
* the pointer is not positioned over the button.
*/
public get upState (): DisplayObject {
// @todo
Debug.throwPIR('playerglobals/display/SimpleButton', 'get upState', '');
return null;
}
public set upState (value: DisplayObject) {
// @todo
Debug.throwPIR('playerglobals/display/SimpleButton', 'set upState', '');
}
/**
* A Boolean value that, when set to true, indicates whether
* the hand cursor is shown when the pointer rolls over a button.
* If this property is set to false, the arrow pointer cursor is displayed
* instead. The default is true.
*
* You can change the useHandCursor property at any time;
* the modified button immediately uses the new cursor behavior.
* @maelexample Create two buttons on the Stage with the instance names
* myBtn1_btn and myBtn2_btn. Enter the following ActionScript in Frame 1 of the Timeline:
*
* myBtn1_btn.useHandCursor = false;
* myBtn1_btn.onRelease = buttonClick;
* myBtn2_btn.onRelease = buttonClick;
* function buttonClick() {
* trace(this._name);
* }
* When the mouse is over and clicks myBtn1_btn,
* there is no pointing hand. However, you see the pointing hand when the button is over and clicks
* myBtn2_btn.
*/
public get useHandCursor (): boolean {
// @todo
Debug.throwPIR('playerglobals/display/SimpleButton', 'get useHandCursor', '');
return false;
}
public set useHandCursor (value: boolean) {
// @todo
Debug.throwPIR('playerglobals/display/SimpleButton', 'set useHandCursor', '');
}
public clone(): SimpleButton {
const anyThis: AssetBase = this;
if (!anyThis._symbol) {
throw ('_symbol not defined when cloning movieclip');
}
const clone: SimpleButton = constructClassFromSymbol(anyThis._symbol, anyThis._symbol.symbolClass);
const adaptee = new AwayMovieClip(( this.adaptee).timeline);
this.adaptee.copyTo(adaptee);
clone.adaptee = adaptee;
clone._stage = this.activeStage;
(clone).executeConstructor = () => {
//adaptee.timeline.resetScripts();
(clone).axInitializer();
(clone).constructorHasRun = true;
};
return clone;
}
}