/********************************************************************** Filename : GFxButton.cpp Content : Implementation of Button character and its AS2 Button class Created : Authors : Michael Antonov, Artem Bolgar Notes : Copyright : (c) 2001-2009 Scaleform Corp. All Rights Reserved. Licensees may use this file in accordance with the valid Scaleform Commercial License Agreement provided with the software. This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR ANY PURPOSE. **********************************************************************/ #include "GFxButton.h" #include "GRenderer.h" #include "GFxAction.h" #include "GFxSound.h" #include "GFxStream.h" #include "GFxFontResource.h" #include "GFxPlayerImpl.h" #include "GFxLoaderImpl.h" #include "GFxSprite.h" #include "AS/GASRectangleObject.h" //#include "GFxLoader.h" #include "GFxAudio.h" #include "GFxSoundTagsReader.h" #include "GFxLoadProcess.h" #include "GFxDisplayContext.h" void GFx_FindClassAndInitializeClassInstance(const GASFnCall& fn); /* Observations about button & mouse behavior Entities that receive mouse events: only buttons and sprites, AFAIK When the mouse button goes down, it becomes "captured" by whatever element is topmost, directly below the mouse at that moment. While the mouse is captured, no other entity receives mouse events, regardless of how the mouse or other elements move. The mouse remains captured until the mouse button goes up. The mouse remains captured even if the element that captured it is removed from the display list. If the mouse isn't above a button or sprite when the mouse button goes down, then the mouse is captured by the Background (i.E. mouse events just don't get sent, until the mouse button goes up again). MA: The only exception to this is Menu mode button, which will get dragOver/dragOut and release even if the inital click was outside of the button (unless it was in a NON-menu button initally, in which case that button would capture the mouse). Mouse events: +------------------+---------------+-------------------------------------+ | Event | Mouse Button | description | ========================================================================= | onRollOver | up | sent to topmost entity when mouse | | | | cursor initially goes over it | +------------------+---------------+-------------------------------------+ | onRollOut | up | when mouse leaves entity, after | | | | onRollOver | +------------------+---------------+-------------------------------------+ | onPress | up -> down | sent to topmost entity when mouse | | | | button goes down. onRollOver | | | | always precedes onPress. Initiates | | | | mouse capture. | +------------------+---------------+-------------------------------------+ | onRelease | down -> up | sent to active entity if mouse goes | | | | up while over the element | +------------------+---------------+-------------------------------------+ | onDragOut | down | sent to active entity if mouse | | | | is no longer over the entity | +------------------+---------------+-------------------------------------+ | onReleaseOutside | down -> up | sent to active entity if mouse goes | | | | up while not over the entity. | | | | onDragOut always precedes | | | | onReleaseOutside | +------------------+---------------+-------------------------------------+ | onDragOver | down | sent to active entity if mouse is | | | | dragged back over it after | | | | onDragOut | +------------------+---------------+-------------------------------------+ There is always one active entity at any given Time (considering NULL to be an active entity, representing the background, and other objects that don't receive mouse events). When the mouse button is up, the active entity is the topmost element directly under the mouse pointer. When the mouse button is down, the active entity remains whatever it was when the button last went down. The active entity is the only object that receives mouse events. !!! The "trackAsMenu" property alters this behavior! If trackAsMenu is set on the active entity, then onReleaseOutside is filtered out, and onDragOver from another entity is Allowed (from the background, or another trackAsMenu entity). !!! */ void GFx_GenerateMouseButtonEvents(UInt mouseIndex, GFxMouseState* ms, UInt checkCount) { GPtr ActiveEntity = ms->GetActiveEntity(); GPtr TopmostEntity = ms->GetTopmostEntity(); UInt changeMask = ms->GetPrevButtonsState() ^ ms->GetButtonsState(); bool suppressRollOut = false; bool miel = ms->IsMouseInsideEntityLast(); for (UInt8 buttonIdx = 0; buttonIdx < checkCount; ++buttonIdx) // PPS: 16 is max number of mouse buttons supported { bool genAuxEvent = buttonIdx != 0; UInt8 buttonMask = 1 << buttonIdx; // Handle button state change if ( ((changeMask >> buttonIdx) & 0x1) != 0 ) { // Button was down if (ms->GetPrevButtonsState() & buttonMask) { // Button is now up // Handle onRelease, onReleaseOutside if ((ms->GetButtonsState() & buttonMask) == 0) { if (ActiveEntity) { if (ms->IsMouseInsideEntityLast()) { // onRelease ActiveEntity->OnButtonEvent(GFxButtonEventId(genAuxEvent ? GFxEventId::Event_ReleaseAux : GFxEventId::Event_Release, mouseIndex, 0, buttonIdx)); } else { // onReleaseOutside if (!ActiveEntity->GetTrackAsMenu()) ActiveEntity->OnButtonEvent(GFxButtonEventId(genAuxEvent ? GFxEventId::Event_ReleaseOutsideAux : GFxEventId::Event_ReleaseOutside, mouseIndex, 0, buttonIdx)); // Prevent Event_RollOut event below. suppressRollOut = true; } } } } // Button was up if ((ms->GetPrevButtonsState() & buttonMask) == 0) { // Button is now down if ((ms->GetButtonsState() & buttonMask)) { // onPress if (ActiveEntity) ActiveEntity->OnButtonEvent(GFxButtonEventId(genAuxEvent ? GFxEventId::Event_PressAux : GFxEventId::Event_Press, mouseIndex, 0, buttonIdx)); miel = true; } } } else if (ms->GetButtonsState() & buttonMask) { // Handle buttons-specific continous polling states // Handle onDragOut, onDragOver if (!ms->IsMouseInsideEntityLast()) { if (TopmostEntity == ActiveEntity) { // onDragOver if (ActiveEntity) ActiveEntity->OnButtonEvent(GFxButtonEventId (genAuxEvent ? GFxEventId::Event_DragOverAux : GFxEventId::Event_DragOver, mouseIndex, ActiveEntity->IncrementRollOverCnt(), buttonIdx)); miel = true; } } else { // MouseInsideEntityLast == true if (TopmostEntity != ActiveEntity) { // onDragOut if (ActiveEntity) { ActiveEntity->OnButtonEvent(GFxButtonEventId (genAuxEvent ? GFxEventId::Event_DragOutAux : GFxEventId::Event_DragOut, mouseIndex, ActiveEntity->DecrementRollOverCnt(), buttonIdx)); } miel = false; } } // Handle trackAsMenu dragOver if (!ActiveEntity || ActiveEntity->GetTrackAsMenu()) { if (TopmostEntity && TopmostEntity != ActiveEntity && TopmostEntity->GetTrackAsMenu()) { // Transfer to topmost entity, dragOver ActiveEntity = TopmostEntity; ActiveEntity->OnButtonEvent(GFxButtonEventId(genAuxEvent ? GFxEventId::Event_DragOverAux : GFxEventId::Event_DragOver, mouseIndex, ActiveEntity->IncrementRollOverCnt(), buttonIdx)); miel = true; } } } } // Handle non-button specific continuous polling states // New active entity is whatever is below the mouse right now. if (ms->GetButtonsState() == 0 && TopmostEntity != ActiveEntity) { // onRollOut if (!suppressRollOut && ActiveEntity) ActiveEntity->OnButtonEvent(GFxButtonEventId (GFxEventId::Event_RollOut, mouseIndex, ActiveEntity->DecrementRollOverCnt())); ActiveEntity = TopmostEntity; // onRollOver if (ActiveEntity) ActiveEntity->OnButtonEvent(GFxButtonEventId (GFxEventId::Event_RollOver, mouseIndex, ActiveEntity->IncrementRollOverCnt())); miel = true; } ms->SetMouseInsideEntityLast(miel); // Write The (possibly modified) GPtr copies back // into the state struct. ms->SetActiveEntity(ActiveEntity); } class GFxButtonCharacter : public GFxASCharacter { public: GFxButtonCharacterDef* pDef; GFxScale9Grid* pScale9Grid; GArrayLH > RecordCharacter; enum MouseFlags { IDLE = 0, FLAG_OVER = 1, FLAG_DOWN = 2, OVER_DOWN = FLAG_OVER|FLAG_DOWN, // aliases OVER_UP = FLAG_OVER, OUT_DOWN = FLAG_DOWN }; int LastMouseFlags, MouseFlags; GFxButtonRecord::MouseState MouseState; GPtr ASButtonObj; GFxButtonCharacter(GFxButtonCharacterDef* def, GFxMovieDefImpl *pbindingDefImpl, GFxASCharacter* parent, GFxResourceId id) : GFxASCharacter(pbindingDefImpl, parent, id), pDef(def), pScale9Grid(0), LastMouseFlags(IDLE), MouseFlags(IDLE), MouseState(GFxButtonRecord::MouseUp) { GASSERT(pDef); SetScale9Grid(def->GetScale9Grid()); SetTrackAsMenuFlag(pDef->Menu); RecordCharacter.Resize(pDef->ButtonRecords.GetSize()); //ASButtonObj = *GHEAP_AUTO_NEW(this) GASButtonObject(GetGC(), this); // let the base class know what's going on //pProto = ASButtonObj->Get__proto__(); pProto = GetGC()->GetActualPrototype(GetASEnvironment(), GASBuiltin_Button); RecreateCharacters(); } ~GFxButtonCharacter() { delete pScale9Grid; } inline void SetDirtyFlag() { GetMovieRoot()->SetDirtyFlag(); } virtual GFxCharacterDef* GetCharacterDef() const { return pDef; } // Default implementation of these is ok for buttons. // In the future, however, objects will have to consider _lockroot (although this may not affect buttons). //virtual GFxMovieDef* GetResourceMovieDef() const { return pParent->GetResourceMovieDef(); } //virtual GFxMovieRoot* GetMovieRoot() const { return pParent->GetMovieRoot(); } //virtual GFxMovieSub* GetLevelMovie(SInt level) const { return pParent->GetLevelMovie(level); } //virtual GFxMovieSub* GetRootMovie2() const { return pParent->GetRootMovie2(); } void SetScale9Grid(const GFxScale9Grid* gr) { bool propagate = (gr != 0) != (pScale9Grid != 0); if (gr == 0) { delete pScale9Grid; pScale9Grid = 0; SetScale9GridExists(false); } else { if (pScale9Grid == 0) pScale9Grid = GHEAP_AUTO_NEW(this) GFxScale9Grid; *pScale9Grid = *gr; SetScale9GridExists(true); } SetDirtyFlag(); if (propagate) PropagateScale9GridExists(); } void PropagateScale9GridExists() { bool actualGrid = GetScale9Grid() != 0; // Stop cleaning up scale9Grid if actual one exists in the node if (!DoesScale9GridExist() && actualGrid) return; for (UInt i = 0; i < RecordCharacter.GetSize(); i++) { GFxCharacter* ch = RecordCharacter[i]; if (!ch) continue; ch->SetScale9GridExists(DoesScale9GridExist() || actualGrid); ch->PropagateScale9GridExists(); } } #ifndef GFC_USE_OLD_ADVANCE void PropagateNoAdvanceGlobalFlag() { bool actualValue = IsNoAdvanceGlobalFlagSet(); GFxMovieRoot* proot = GetMovieRoot(); if (!proot) return; for (UInt i = 0; i < RecordCharacter.GetSize(); i++) { GFxCharacter* _ch = RecordCharacter[i]; if (!_ch) continue; GFxASCharacter* ch = _ch->CharToASCharacter(); if (ch) { ch->SetNoAdvanceGlobalFlag(IsNoAdvanceGlobalFlagSet() || actualValue); ch->PropagateNoAdvanceGlobalFlag(); ch->ModifyOptimizedPlayList(proot); } } } void PropagateNoAdvanceLocalFlag() { bool actualValue = IsNoAdvanceLocalFlagSet(); GFxMovieRoot* proot = GetMovieRoot(); if (!proot) return; for (UInt i = 0; i < RecordCharacter.GetSize(); i++) { GFxCharacter* _ch = RecordCharacter[i]; if (!_ch) continue; GFxASCharacter* ch = _ch->CharToASCharacter(); if (ch) { ch->SetNoAdvanceLocalFlag(IsNoAdvanceLocalFlagSet() || actualValue); ch->PropagateNoAdvanceLocalFlag(); ch->ModifyOptimizedPlayList(proot); } } } #endif //#ifndef GFC_USE_OLD_ADVANCE void SetVisible(bool visible) { SetVisibleFlag(visible); GFxMovieRoot* proot = GetMovieRoot(); if (!proot) return; #ifndef GFC_USE_OLD_ADVANCE bool noAdvGlob = !visible && proot->IsNoInvisibleAdvanceFlagSet(); if (noAdvGlob != IsNoAdvanceGlobalFlagSet()) { SetNoAdvanceGlobalFlag(noAdvGlob); ModifyOptimizedPlayListLocal(proot); GFxASCharacter* pparent = GetParent(); if (pparent && !pparent->IsNoAdvanceGlobalFlagSet()) PropagateNoAdvanceGlobalFlag(); } #else SetNoAdvanceGlobalFlag(!visible && proot->IsNoInvisibleAdvanceFlagSet()); #endif SetDirtyFlag(); } void Restart() { LastMouseFlags = IDLE; MouseFlags = IDLE; MouseState = GFxButtonRecord::MouseUp; RollOverCnt = 0; UPInt r, R_num = RecordCharacter.GetSize(); for (r = 0; r < R_num; r++) { if (RecordCharacter[r]) RecordCharacter[r]->Restart(); } SetDirtyFlag(); } // Obtains an active button record based on state, or -1. // Note: not quite correct, since multiple records can apply to a state. int GetActiveRecordIndex() const { return GetRecordIndex(MouseState); } int GetRecordIndex(GFxButtonRecord::MouseState mouseState) const { for (UInt i = 0; i < pDef->ButtonRecords.GetSize(); i++) { GFxButtonRecord& rec = pDef->ButtonRecords[i]; if (!RecordCharacter[i]) continue; if (rec.MatchMouseState(mouseState)) { return i; } } // Not found return -1; } void OnEventUnload() { for (UInt i = 0; i < pDef->ButtonRecords.GetSize(); i++) { UnloadCharacterAtIndex(i); } GFxASCharacter::OnEventUnload(); } #ifdef GFC_USE_OLD_ADVANCE int CheckAdvanceStatus(bool playingNow) { return (playingNow) ? -1 : 0; } virtual void AdvanceFrame(bool nextFrame, Float framePos) { // Implement mouse-drag. GFxASCharacter::DoMouseDrag(); for (UInt i = 0; i < pDef->ButtonRecords.GetSize(); i++) { GFxButtonRecord& rec = pDef->ButtonRecords[i]; if (RecordCharacter[i] && rec.MatchMouseState(MouseState)) RecordCharacter[i]->AdvanceFrame(nextFrame, framePos); } } #endif //ifdef GFC_USE_OLD_ADVANCE void Display(GFxDisplayContext& context) { if (!pDef->ButtonRecords.GetSize()) return; // We must apply a new binding table if the character needs to // use resource data from the loaded/imported GFxMovieDefImpl. GFxResourceBinding *psave = context.pResourceBinding; context.pResourceBinding = &pDefImpl->GetResourceBinding(); GFxMovieRoot* proot = GetMovieRoot(); if (!proot) return; GRenderer* prenderer = context.GetRenderer(); // save context transform pointers GFxDisplayContextTransforms oldXform; GFxDisplayContextFilters oldFilters; context.CopyTransformsTo(&oldXform); GRenderer::Cxform wcx; GMatrix2D matrix; #ifndef GFC_NO_3D GMatrix3D matrix3D; GMatrix3D *pmatrix3D = &matrix3D; #else GMatrix3D *pmatrix3D = NULL; #endif // not correct, doesn't support prepass, use Sprite behavior bool useFilters = (proot->pRenderConfig->GetRendererCapBits() & GRenderer::Cap_RenderTargets) != 0; // Button records already sorted by depth, so display them in order. for (UInt i = 0; i < pDef->ButtonRecords.GetSize(); i++) { GFxButtonRecord& rec = pDef->ButtonRecords[i]; GFxCharacter* pch = RecordCharacter[i]; if (pch && rec.MatchMouseState(MouseState)) { // prepare context transform pointers for display context.PreDisplay(oldXform, this, &matrix, pmatrix3D, &wcx); prenderer->PushBlendMode(pch->GetBlendMode()); bool filtered = useFilters && context.BeginFilters(oldFilters, pch, rec.Filters); pch->Display(context); if (filtered) context.EndFilters(oldFilters, pch, rec.Filters); prenderer->PopBlendMode(); } } context.pResourceBinding = psave; context.PostDisplay(oldXform); DoDisplayCallback(); } // Combine the flags to avoid a conditional. It would be faster with a macro. GINLINE int Transition(int a, int b) const { return (a << 2) | b; } virtual bool PointTestLocal(const GPointF &pt, UInt8 hitTestMask = 0) const { if (IsHitTestDisableFlagSet()) return false; if ((hitTestMask & HitTest_IgnoreInvisible) && !GetVisible()) return false; if (!DoesScale9GridExist()) { if (!GetBounds(GRenderer::Matrix()).Contains(pt)) return false; else if (!(hitTestMask & HitTest_TestShape)) return true; } for (UInt i = 0; i < pDef->ButtonRecords.GetSize(); i++) { GFxButtonRecord& rec = pDef->ButtonRecords[i]; if ((rec.CharacterId == GFxResourceId::InvalidId) || !rec.IsHitTest()) continue; GFxCharacter* ch = RecordCharacter[i]; if (!ch) continue; if ((hitTestMask & HitTest_IgnoreInvisible) && !ch->GetVisible()) continue; GRenderer::Matrix m = ch->GetMatrix(); GPointF p = m.TransformByInverse(pt); if (ch->PointTestLocal (p, hitTestMask)) return true; } return false; } // Return the topmost entity that the given point covers. NULL if none. // I.E. check against ourself. virtual GFxASCharacter* GetTopMostMouseEntity(const GPointF &pt, const TopMostParams& params) { if (!GetVisible()) return 0; if (params.IgnoreMC == this) return 0; if (!IsFocusAllowed(params.pRoot, params.ControllerIdx)) return 0; GRenderer::Matrix m = GetMatrix(); GPointF p; #ifndef GFC_NO_3D if (Is3D(true)) { const GMatrix3D *pPersp = GetPerspective3D(true); const GMatrix3D *pView = GetView3D(true); if (pPersp) GetMovieRoot()->ScreenToWorld.SetPerspective(*pPersp); if (pView) GetMovieRoot()->ScreenToWorld.SetView(*pView); GetMovieRoot()->ScreenToWorld.SetWorld(GetWorldMatrix3D()); GetMovieRoot()->ScreenToWorld.GetWorldPoint(&p); } else #endif { m.TransformByInverse(&p, pt); } for (UInt i = 0; i < pDef->ButtonRecords.GetSize(); i++) { GFxButtonRecord& rec = pDef->ButtonRecords[i]; if ((rec.CharacterId == GFxResourceId::InvalidId) || !rec.IsHitTest()) continue; if (i < RecordCharacter.GetSize()) { GFxCharacter* pcharacter = RecordCharacter[i]; if (pcharacter) { GRenderer::Matrix m = pcharacter->GetMatrix(); GPointF subp; #ifndef GFC_NO_3D if (pcharacter->Is3D(true)) { const GMatrix3D *pPersp = pcharacter->GetPerspective3D(true); const GMatrix3D *pView = pcharacter->GetView3D(true); if (pPersp) GetMovieRoot()->ScreenToWorld.SetPerspective(*pPersp); if (pView) GetMovieRoot()->ScreenToWorld.SetView(*pView); GetMovieRoot()->ScreenToWorld.SetWorld(pcharacter->GetWorldMatrix3D()); GetMovieRoot()->ScreenToWorld.GetWorldPoint(&subp); } else #endif { subp = m.TransformByInverse(p); } if (pcharacter->PointTestLocal (subp, HitTest_TestShape)) return this; } } } return NULL; } bool IsTabable() const { if (!GetVisible()) return false; if (!IsTabEnabledFlagDefined()) { GASObject* pproto = Get__proto__(); if (pproto) { // check prototype for tabEnabled GASValue val; const GASEnvironment* penv = GetASEnvironment(); if (pproto->GetMemberRaw(penv->GetSC(), penv->CreateConstString("tabEnabled"), &val)) { if (!val.IsUndefined()) return val.ToBool(penv); } } return true; } else return IsTabEnabledFlagTrue(); } virtual ObjectType GetObjectType() const { return Object_Button; } virtual const GFxScale9Grid* GetScale9Grid() const { return pScale9Grid; } // Return a single character bounds virtual GRectF GetBoundsOfRecord(const Matrix &transform, UInt recNumber) const { // Custom based on state. GRectF bounds(0); Matrix m; if (RecordCharacter[recNumber]) { m = transform; m.Prepend(RecordCharacter[recNumber]->GetMatrix()); bounds = RecordCharacter[recNumber]->GetBounds(m); } return bounds; } // Return a single character "pure rectangle" bounds (not considering the stroke) GRectF GetRectBounds(const Matrix &transform, UInt recNumber) const { // Custom based on state. GRectF bounds(0); Matrix m; if (RecordCharacter[recNumber]) { m = transform; m.Prepend(RecordCharacter[recNumber]->GetMatrix()); bounds = RecordCharacter[recNumber]->GetRectBounds(m); } return bounds; } // Get bounds. This is used, // among other things, to calculate button width & height. virtual GRectF GetBounds(const Matrix &transform) const { // Custom based on state. GRectF bounds(0); GRectF tempRect; bool boundsInit = 0; for (UInt i = 0; i < pDef->ButtonRecords.GetSize(); i++) { GFxButtonRecord& rec = pDef->ButtonRecords[i]; if (rec.MatchMouseState(MouseState)) { tempRect = GetBoundsOfRecord(transform, i); if (!tempRect.IsNull()) { if (!boundsInit) { bounds = tempRect; boundsInit = 1; } else { bounds.Union(tempRect); } } } } return bounds; } // "transform" matrix describes the transform applied to parent and us, // including the object's matrix itself. This means that if transform is // identity, GetBoundsTransformed will return local bounds and NOT parent bounds. virtual GRectF GetRectBounds(const Matrix &transform) const { // Custom based on state. GRectF bounds(0); GRectF tempRect; bool boundsInit = 0; for (UInt i = 0; i < pDef->ButtonRecords.GetSize(); i++) { // TO DO: Clarify this, whether or not we should match the state. // If yes, it may change the scale9grid configuration and the appearance. //GFxButtonRecord& rec = pDef->ButtonRecords[i]; //if (rec.MatchMouseState(MouseState)) { tempRect = GetRectBounds(transform, i); if (!tempRect.IsNull()) { if (!boundsInit) { bounds = tempRect; boundsInit = 1; } else { bounds.Union(tempRect); } } } } return bounds; } enum ButtonState { Up, Over, Down, Hit }; // returns the local boundaries of whole state virtual GRectF GetBoundsOfState(const Matrix &transform, ButtonState state) const { // Custom based on state. GRectF bounds(0); GRectF tempRect; for (UInt i = 0; i < pDef->ButtonRecords.GetSize(); i++) { GFxButtonRecord& rec = pDef->ButtonRecords[i]; if ((state == Hit && rec.IsHitTest()) || (state == Down && rec.IsDown()) || (state == Over && rec.IsOver()) || (state == Up && rec.IsUp())) { tempRect = GetBoundsOfRecord(transform, i); if (!tempRect.IsNull()) { if (bounds.IsNull()) bounds = tempRect; else bounds.Union(tempRect); } } } return bounds; } // focus rect for buttons is calculated as follows: // 1) if "hit" state exists - boundary rect of "hit" shape // 2) if "down" state exists - boundary rect of "down" shape // 3) if "over" state exists - boundary rect of "over" shape // 4) otherwise - boundary rect of "up" shape virtual GRectF GetFocusRect() const { GRenderer::Matrix m; GRectF tempRect; if (!(tempRect = GetBoundsOfState(m, Hit)).IsNull()) return tempRect; else if (!(tempRect = GetBoundsOfState(m, Down)).IsNull()) return tempRect; else if (!(tempRect = GetBoundsOfState(m, Over)).IsNull()) return tempRect; else if (!(tempRect = GetBoundsOfState(m, Up)).IsNull()) return tempRect; return GetBounds(m); // shouldn't reach this point, actually } // invoked when item is going to get focus (Selection.setFocus is invoked, or TAB is pressed) void OnGettingKeyboardFocus(UInt /*controllerIdx*/) { GFxMovieRoot* proot = GetMovieRoot(); if (proot && !proot->IsDisableFocusRolloverEvent()) OnButtonEvent(GFxEventId::Event_RollOver); } // invoked when focused item is about to lose keyboard focus input (mouse moved, for example) bool OnLosingKeyboardFocus(GFxASCharacter*, UInt controllerIdx, GFxFocusMovedType) { GFxMovieRoot* proot = GetMovieRoot(); if (proot->IsFocusRectShown(controllerIdx) && !proot->IsDisableFocusRolloverEvent()) OnButtonEvent(GFxEventId::Event_RollOut); return true; } void PropagateMouseEvent(const GFxEventId& event) { if (event.Id == GFxEventId::Event_MouseMove) { // Implement mouse-drag. GFxASCharacter::DoMouseDrag(); } GFxASCharacter::PropagateMouseEvent(event); } /* returns true, if event fired */ virtual bool OnButtonEvent(const GFxEventId& event) { if (IsUnloading() || IsUnloaded()) return false; // Enabled buttons are not manipulated by events. if (!IsEnabledFlagSet()) return false; bool handlerFound = false; //GFxButtonRecord::MouseState prevMouseState = MouseState; if (event.RollOverCnt == 0) // RollOverCnt is not zero only when multiple mice used // and only for onRoll/Drag/Over/Out events { // Set our mouse State (so we know how to render). switch (event.Id) { case GFxEventId::Event_RollOut: case GFxEventId::Event_ReleaseOutside: MouseState = GFxButtonRecord::MouseUp; break; case GFxEventId::Event_Release: case GFxEventId::Event_RollOver: MouseState = GFxButtonRecord::MouseOver; break; case GFxEventId::Event_DragOut: if (GetTrackAsMenu()) MouseState = GFxButtonRecord::MouseUp; else MouseState = GFxButtonRecord::MouseOver; break; case GFxEventId::Event_Press: case GFxEventId::Event_DragOver: MouseState = GFxButtonRecord::MouseDown; break; case GFxEventId::Event_KeyPress: break; default: return false; // missed a case? }; #ifndef GFC_NO_SOUND // Button transition sounds. if (pDef->pSound != NULL) { int bi; // button sound GArray index [0..3] switch (event.Id) { case GFxEventId::Event_RollOut: bi = 0; break; case GFxEventId::Event_RollOver: bi = 1; break; case GFxEventId::Event_Press: bi = 2; break; case GFxEventId::Event_Release: bi = 3; break; default: bi = -1; break; } pDef->pSound->Play(this, bi); } #endif // GFC_NO_SOUND // @@ eh, should just be a lookup table. int c = 0, kc = 0; if (event.Id == GFxEventId::Event_RollOver) c |= (GFxButtonAction::IDLE_TO_OVER_UP); else if (event.Id == GFxEventId::Event_RollOut) c |= (GFxButtonAction::OVER_UP_TO_IDLE); else if (event.Id == GFxEventId::Event_Press) c |= (GFxButtonAction::OVER_UP_TO_OVER_DOWN); else if (event.Id == GFxEventId::Event_Release) c |= (GFxButtonAction::OVER_DOWN_TO_OVER_UP); else if (event.Id == GFxEventId::Event_DragOut) c |= (GFxButtonAction::OVER_DOWN_TO_OUT_DOWN); else if (event.Id == GFxEventId::Event_DragOver) c |= (GFxButtonAction::OUT_DOWN_TO_OVER_DOWN); else if (event.Id == GFxEventId::Event_ReleaseOutside) c |= (GFxButtonAction::OUT_DOWN_TO_IDLE); else if (event.Id == GFxEventId::Event_KeyPress) { // convert keycode/ascii to button's keycode kc = event.ConvertToButtonKeyCode(); } //IDLE_TO_OVER_DOWN = 1 << 7, //OVER_DOWN_TO_IDLE = 1 << 8, // recreate the characters of the new state and release ones for previous state. RecreateCharacters(); GFxASCharacter* pparent = GetParent(); if (pparent) { GFxSprite* pparentSprite = pparent->ToSprite(); if (pparentSprite) { // Add appropriate actions to the GFxMovieSub's execute list... for (UInt i = 0; i < pDef->ButtonActions.GetSize(); i++) { if (((pDef->ButtonActions[i].Conditions & (~0xFE00)) & c) || //!AB?? (kc > 0 && ((pDef->ButtonActions[i].Conditions >> 9) & 0x7F) == kc)) { // Matching action. GASStringContext* psc = pparentSprite->GetASEnvironment()->GetSC(); const UPInt n = pDef->ButtonActions[i].Actions.GetSize(); for (UPInt j = 0; j < n; ++j) { if (!pDef->ButtonActions[i].Actions[j]->IsNull()) { GPtr pbuff = *GHEAP_NEW(psc->GetHeap()) GASActionBuffer(psc, pDef->ButtonActions[i].Actions[j]); pparentSprite->AddActionBuffer(pbuff); } } if (n > 0) handlerFound = true; } } } } //? ModifyOptimizedPlayListLocal(GetMovieRoot()); } // Call conventional attached method. // Check for member function, it is called after onClipEvent(). // In ActionScript 2.0, event method names are CASE SENSITIVE. // In ActionScript 1.0, event method names are CASE INSENSITIVE. GASEnvironment *penv = GetASEnvironment(); if (penv) { // penv can be NULL, if the character has already been removed // from the display list by earlier handlers (for example, by // "Mouse" class listeners, or by "on(...)" handler). GASString methodName(event.GetFunctionName(penv->GetSC())); if (methodName.GetSize() > 0) { GASValue method; if (GetMemberRaw(penv->GetSC(), methodName, &method)) { handlerFound = true; // do actual dispatch GFxMovieRoot::ActionEntry* pe = GetMovieRoot()->InsertEmptyAction(); if (pe) pe->SetAction(this, event); } } } return handlerFound; } void UnloadCharacterAtIndex(UInt i) { // unload character if (RecordCharacter[i]) { GFxCharacter* ch = RecordCharacter[i]; GFxASCharacter* pscriptCh = ch->CharToASCharacter(); if (pscriptCh) { bool mayRemove = ch->OnUnloading(); ch->SetUnloading(true); if (mayRemove) { // We can remove the object instantly, so, do it now. ch->OnEventUnload(); } //else //{ // GFxMovieRoot* proot = GetMovieRoot(); // GASSERT(proot); // pscriptCh->RemoveFromPlaylist(proot); //} } RecordCharacter[i] = NULL; } } void RecreateCharacters() { // Restart our relevant characters for (UInt i = 0; i < pDef->ButtonRecords.GetSize(); i++) { GFxButtonRecord* rec = &pDef->ButtonRecords[i]; if (!RecordCharacter[i]) { if (rec->MatchMouseState(MouseState) || rec->IsHitTest()) { // create character // Resolve the GFxCharacter id. GFxCharacterCreateInfo ccinfo = pDefImpl->GetCharacterCreateInfo(rec->CharacterId); GASSERT(ccinfo.pCharDef && ccinfo.pBindDefImpl); if (ccinfo.pCharDef) { const GRenderer::Matrix& mat = pDef->ButtonRecords[i].ButtonMatrix; const GRenderer::Cxform& cx = pDef->ButtonRecords[i].ButtonCxform; GPtr ch = *ccinfo.pCharDef->CreateCharacterInstance(this, rec->CharacterId, ccinfo.pBindDefImpl); RecordCharacter[i] = ch; ch->SetMatrix(mat); ch->SetCxform(cx); ch->SetBlendMode(rec->BlendMode); ch->SetScale9GridExists(false); const GFxASCharacter* parent = ch->GetParent(); while (parent) { if (parent->GetScale9Grid()) { ch->SetScale9GridExists(true); ch->PropagateScale9GridExists(); break; } parent = parent->GetParent(); } GFxASCharacter* pscriptCh = ch->CharToASCharacter(); if (pscriptCh) { GFxMovieRoot* proot = GetMovieRoot(); GASSERT(proot); if (pscriptCh->IsSprite()) { GASGlobalContext* gctxt = this->GetGC(); GASSERT(gctxt); GASFunctionRef ctorFunc; const GString* psymbolName = ch->GetResourceMovieDef()->GetNameOfExportedResource(rec->CharacterId); if (psymbolName) { GASString symbolName = GetASEnvironment()->CreateString(*psymbolName); if (gctxt->FindRegisteredClass(GetASEnvironment()->GetSC(), symbolName, &ctorFunc)) { pscriptCh->SetProtoToPrototypeOf(ctorFunc.GetObjectPtr()); // schedule "construct" event, if any //if (hasConstructEvent) //!AB, fire onConstruct always, since it is impossible { // to check whether onConstruct method exists or not GFxMovieRoot::ActionEntry* pe = proot->InsertEmptyAction(GFxMovieRoot::AP_Construct); if (pe) pe->SetAction(pscriptCh, GFxEventId::Event_Construct); } // now, schedule constructor invocation GFxMovieRoot::ActionEntry* pe = proot->InsertEmptyAction(GFxMovieRoot::AP_Construct); if (pe) pe->SetAction(pscriptCh, ctorFunc); } else { GASValueArray params; params.PushBack(GASValue(symbolName)); //[0] //params.PushBack(GASValue(hasConstructEvent)); //[1] GFxMovieRoot::ActionEntry* pe = proot->InsertEmptyAction(GFxMovieRoot::AP_Construct); if (pe) pe->SetAction(pscriptCh, GFx_FindClassAndInitializeClassInstance, ¶ms); } } } pscriptCh->AddToPlayList(proot); pscriptCh->ModifyOptimizedPlayList(proot); ch->OnEventLoad(); } } } } else { if (!rec->MatchMouseState(MouseState) && !rec->IsHitTest()) { // unload character UnloadCharacterAtIndex(i); } } } } // // ActionScript overrides // // GFxASCharacter override to indicate which standard members are handled for us. virtual UInt32 GetStandardMemberBitMask() const { // MovieClip lets base handle all members it supports. return UInt32( M_BitMask_PhysicalMembers | M_BitMask_CommonMembers | (1 << M_target) | (1 << M_url) | (1 << M_parent) | (1 << M_blendMode) | (1 << M_cacheAsBitmap) | (1 << M_filters) | (1 << M_focusrect) | (1 << M_enabled) | (1 << M_trackAsMenu) | (1 << M_tabEnabled) | (1 << M_tabIndex) | (1 << M_useHandCursor) | (1 << M_quality) | (1 << M_highquality) | (1 << M_soundbuftime) | (1 << M_xmouse) | (1 << M_ymouse) ); // MA Verified: _lockroot does not exist/carry over from buttons, so don't include it. // If a movie is loaded into button, local _lockroot state is lost. } GASButtonObject* GetButtonASObject() { if (!ASButtonObj) { ASButtonObj = *GHEAP_AUTO_NEW(this) GASButtonObject(GetGC(), this); } return ASButtonObj; } virtual GASObject* GetASObject () { return GetButtonASObject(); } virtual GASObject* GetASObject () const { return const_cast(this)->GetButtonASObject(); } virtual bool GetMember(GASEnvironment* penv, const GASString& name, GASValue* pval) { if (name.IsStandardMember()) if (GetStandardMember(GetStandardMemberConstant(name), pval, 0)) return true; if (ASButtonObj) { return ASButtonObj->GetMember(penv, name, pval); } else { // Handle the __proto__ property here, since we are going to // zero out it temporarily (see comments below). if (penv && name == penv->GetBuiltin(GASBuiltin___proto__)) { GASObject* proto = Get__proto__(); pval->SetAsObject(proto); return true; } // Now we can search in the __proto__ GASObject* proto = Get__proto__(); if (proto) { // ASMovieClipObj is not created yet; use __proto__ instead if (proto->GetMember(penv, name, pval)) { return true; } } } // Looks like _global is accessible from any character if (penv && (name == penv->GetBuiltin(GASBuiltin__global))) { pval->SetAsObject(penv->GetGC()->pGlobal); return true; } return false; } virtual bool GetStandardMember(StandardMember member, GASValue* pval, bool opcodeFlag) const { if (GFxASCharacter::GetStandardMember (member, pval, opcodeFlag)) return true; // Handle MovieClip specific "standard" members. switch(member) { case M_scale9Grid: if (GetASEnvironment()->GetVersion() >= 8) { if (pScale9Grid) { GASEnvironment* penv = const_cast(GetASEnvironment()); #ifndef GFC_NO_FXPLAYER_AS_RECTANGLE GPtr rectObj = *GHEAP_NEW(penv->GetHeap()) GASRectangleObject(penv); GASRect gr(TwipsToPixels(pScale9Grid->x), TwipsToPixels(pScale9Grid->y), TwipsToPixels(pScale9Grid->x+pScale9Grid->w), TwipsToPixels(pScale9Grid->y+pScale9Grid->h)); rectObj->SetProperties(penv, gr); #else GPtr rectObj = *GHEAP_NEW(penv->GetHeap()) GASObject(penv); GASStringContext *psc = penv->GetSC(); rectObj->SetConstMemberRaw(psc, "x", TwipsToPixels(pScale9Grid->x)); rectObj->SetConstMemberRaw(psc, "y", TwipsToPixels(pScale9Grid->y)); rectObj->SetConstMemberRaw(psc, "width", TwipsToPixels(pScale9Grid->x+pScale9Grid->w)); rectObj->SetConstMemberRaw(psc, "height", TwipsToPixels(pScale9Grid->y+pScale9Grid->h)); #endif pval->SetAsObject(rectObj); } else { pval->SetUndefined(); } return true; } break; // extension case M_hitTestDisable: if (GetASEnvironment()->CheckExtensions()) { pval->SetBool(IsHitTestDisableFlagSet()); return 1; } break; default: break; } return false; } virtual bool SetStandardMember(StandardMember member, const GASValue& origVal, bool opcodeFlag) { GASValue val(origVal); GASEnvironment* penv = GetASEnvironment(); if (member > M_BuiltInProperty_End) { // Check, if there are watch points set. Invoke, if any. if (penv && GetButtonASObject() && ASButtonObj->HasWatchpoint()) // have set any watchpoints? { GASValue newVal; if (ASButtonObj->InvokeWatchpoint(penv, penv->CreateConstString(GFxASCharacter::MemberTable[member].pName), val, &newVal)) { val = newVal; } } } if (GFxASCharacter::SetStandardMember (member, val, opcodeFlag)) return true; // Handle MovieClip specific "standard" members. switch(member) { case M_scale9Grid: if (GetASEnvironment()->GetVersion() >= 8) { GASEnvironment* penv = GetASEnvironment(); GASObject* pobj = val.ToObject(penv); #ifndef GFC_NO_FXPLAYER_AS_RECTANGLE if (pobj && pobj->GetObjectType() == Object_Rectangle) { GASRectangleObject* prect = (GASRectangleObject*)pobj; GASRect gr; prect->GetProperties(penv, gr); GFxScale9Grid sg; sg.x = PixelsToTwips(Float(gr.Left)); sg.y = PixelsToTwips(Float(gr.Top)); sg.w = PixelsToTwips(Float(gr.Width())); sg.h = PixelsToTwips(Float(gr.Height())); SetScale9Grid(&sg); } #else if (pobj) { GASStringContext *psc = penv->GetSC(); GASValue params[4]; pobj->GetConstMemberRaw(psc, "x", ¶ms[0]); pobj->GetConstMemberRaw(psc, "y", ¶ms[1]); pobj->GetConstMemberRaw(psc, "width", ¶ms[2]); pobj->GetConstMemberRaw(psc, "height", ¶ms[3]); GFxScale9Grid sg; sg.x = PixelsToTwips(Float(params[0].ToNumber(penv))); sg.y = PixelsToTwips(Float(params[1].ToNumber(penv))); sg.w = PixelsToTwips(Float(params[2].ToNumber(penv))); sg.h = PixelsToTwips(Float(params[3].ToNumber(penv))); SetScale9Grid(&sg); } #endif else SetScale9Grid(0); return true; } break; // extension case M_hitTestDisable: if (GetASEnvironment()->CheckExtensions()) { SetHitTestDisableFlag(val.ToBool(GetASEnvironment())); return 1; } break; // No other custom properties to set for now. default: break; } return false; } virtual bool OnKeyEvent(const GFxEventId& id, int* pkeyMask) { // Check for member function. // In ActionScript 2.0, event method names are CASE SENSITIVE. // In ActionScript 1.0, event method names are CASE INSENSITIVE. GASEnvironment *penv = GetASEnvironment(); GASString methodName(id.GetFunctionName(penv->GetSC())); GFxMovieRoot* proot = GetMovieRoot(); if (methodName.GetSize() > 0) { GASValue method; if ((id.Id == GFxEventId::Event_KeyDown || id.Id == GFxEventId::Event_KeyUp) && GetMemberRaw(penv->GetSC(), methodName, &method)) { // onKeyDown/onKeyUp are available only in Flash 6 and later // (don't mess with onClipEvent (keyDown/keyUp)!) if (penv->GetVersion() >= 6 && proot->IsKeyboardFocused(this, id.KeyboardIndex)) { // also, onKeyDown/onKeyUp should be invoked only if focus // is enabled and set to this button // do actual dispatch GFxMovieRoot::ActionEntry* pe = proot->InsertEmptyAction(); if (pe) pe->SetAction(this, id); } } } if (id.Id == GFxEventId::Event_KeyDown) { // covert Event_KeyDown to Event_KeyPress GASSERT (pkeyMask != 0); // check if keyPress already was handled then do not handle it again if (!((*pkeyMask) & GFxCharacter::KeyMask_KeyPress)) { if (OnButtonEvent(GFxEventId (GFxEventId::Event_KeyPress, id.KeyCode, id.AsciiCode))) { *pkeyMask |= GFxCharacter::KeyMask_KeyPress; } } if (proot->IsKeyboardFocused(this, id.KeyboardIndex) && (id.KeyCode == GFxKey::Return || id.KeyCode == GFxKey::Space)) { if (IsFocusRectEnabled() || proot->IsAlwaysEnableKeyboardPress()) { // if focused and enter - simulate on(press)/onPress and on(release)/onRelease OnButtonEvent(GFxEventId(GFxEventId::Event_Press, GFxKey::Return, 0, 0, id.KeyboardIndex)); GPtr thisHolder = this; proot->Advance(0, 0); //??AB, allow on(press) to be executed // completely before on(release). Otherwise, these events may affect each other // (see focusKB_test.swf). Need further investigations. OnButtonEvent(GFxEventId(GFxEventId::Event_Release, GFxKey::Return, 0, 0, id.KeyboardIndex)); } } } return true; } // Set state changed flags virtual void SetStateChangeFlags(UInt8 flags) { GFxASCharacter::SetStateChangeFlags(flags); UPInt size = RecordCharacter.GetSize(); for (UPInt i = 0; i < size; i++) { GFxCharacter* ch = RecordCharacter[i]; if (ch) ch->SetStateChangeFlags(flags); } } UInt GetCursorType() const { const GASEnvironment* penv = GetASEnvironment(); GASValue val; // Theoretically, penv could be NULL if parent is null (means, // object is already disconnected). if (penv && IsEnabledFlagSet()) { if (ASButtonObj) const_cast(this)->GetMemberRaw (penv->GetSC(), penv->GetBuiltin(GASBuiltin_useHandCursor), &val); else if (pProto) pProto->GetMemberRaw (penv->GetSC(), penv->GetBuiltin(GASBuiltin_useHandCursor), &val); if (val.ToBool(penv)) return GFxMouseCursorEvent::HAND; } return GFxASCharacter::GetCursorType(); } }; // // GFxButtonRecord // // Return true if we read a record; false if this is a null record. bool GFxButtonRecord::Read(GFxLoadProcess* p, GFxTagType tagType) { int flags = p->ReadU8(); if (flags == 0) return false; GFxStream* pin = p->GetStream(); pin->LogParse("-- action record: "); Flags = 0; if (flags & 8) { Flags |= Mask_HitTest; pin->LogParse("HitTest "); } if (flags & 4) { Flags |= Mask_Down; pin->LogParse("Down "); } if (flags & 2) { Flags |= Mask_Over; pin->LogParse("Over "); } if (flags & 1) { Flags |= Mask_Up; pin->LogParse("Up "); } pin->LogParse("\n"); int characterId = p->ReadU16(); CharacterId = GFxResourceId(characterId); ButtonLayer = p->ReadU16(); pin->ReadMatrix(&ButtonMatrix); pin->LogParse(" CharId = %d, ButtonLayer = %d\n", characterId, ButtonLayer); pin->LogParse(" mat:\n"); pin->LogParseClass(ButtonMatrix); if (tagType == 34) { pin->ReadCxformRgba(&ButtonCxform); pin->LogParse(" cxform:\n"); pin->LogParseClass(ButtonCxform); } // SWF 8 features. // Has filters. if (flags & 0x10) { pin->LogParse(" HasFilters\n"); // This loads only Blur and DropShadow/Glow filters only. GFxFilterDesc filters[GFxFilterDesc::MaxFilters]; UInt numFilters = GFx_LoadFilters(pin, filters, GFxFilterDesc::MaxFilters); if (numFilters) { #if 0 if (!Filters) Filters = *new GArrayLH; for (UInt i = 0; i < numFilters; ++i) { Filters.Append(filters, numFilters); } #else Filters.Clear(); Filters.Append(filters, numFilters); #endif } } // Has custom blending. if (flags & 0x20) { UByte blendMode = pin->ReadU8(); if ((blendMode < 1) || (blendMode>14)) { GFC_DEBUG_WARNING(1, "ButtonRecord::Read - loaded blend mode out of range"); blendMode = 1; } BlendMode = (GRenderer::BlendType) blendMode; pin->LogParse(" HasBlending, %d\n", (int)BlendMode); } else { BlendMode = GRenderer::Blend_None; } // Note: 'Use Bitmap Caching' toggle does not seem to be serialized to flags, perhaps // because it makes no sense in button records, since they cannot be animated (?). return true; } // // GFxButtonAction // GFxButtonAction::~GFxButtonAction() { for (UPInt i = 0, n = Actions.GetSize(); i < n; i++) { Actions[i]->Release(); } Actions.Resize(0); } void GFxButtonAction::Read(GFxStream* pin, GFxTagType tagType, UInt actionLength) { if (actionLength == 0) return; // Read condition flags. if (tagType == GFxTag_ButtonCharacter) { Conditions = OVER_DOWN_TO_OVER_UP; } else { GASSERT(tagType == GFxTag_ButtonCharacter2); Conditions = pin->ReadU16(); actionLength -= 2; } pin->LogParse("-- action conditions %X\n", Conditions); // Read actions. pin->LogParseAction("-- actions in button\n"); // @@ need more info about which actions GASActionBufferData* a = GASActionBufferData::CreateNew(); a->Read(pin, actionLength); Actions.PushBack(a); } // // GFxButtonCharacterDef // GFxButtonCharacterDef::GFxButtonCharacterDef() : #ifndef GFC_NO_SOUND pSound(NULL), #endif// GFC_NO_SOUND pScale9Grid(NULL) // Constructor. { } GFxButtonCharacterDef::~GFxButtonCharacterDef() { #ifndef GFC_NO_SOUND delete pSound; #endif // GFC_NO_SOUND delete pScale9Grid; } static void SkipButtonSoundDef(GFxLoadProcess* p) { GFxStream *in = p->GetStream(); for (int i = 0; i < 4; i++) { UInt16 rid = p->ReadU16(); if (rid == 0) continue; in->ReadUInt(2); // skip reserved bits. in->ReadUInt(1); in->ReadUInt(1); bool HasEnvelope = in->ReadUInt(1) ? true : false; bool HasLoops = in->ReadUInt(1) ? true : false; bool HasOutPoint = in->ReadUInt(1) ? true : false; bool HasInPoint = in->ReadUInt(1) ? true : false; if (HasInPoint) in->ReadU32(); if (HasOutPoint) in->ReadU32(); if (HasLoops) in->ReadU16(); if (HasEnvelope) { int nPoints = in->ReadU8(); for (int i=0; i < nPoints; i++) { in->ReadU32(); in->ReadU16(); in->ReadU16(); } } } } // Initialize from the given GFxStream. void GFxButtonCharacterDef::Read(GFxLoadProcess* p, GFxTagType tagType) { GASSERT(tagType == GFxTag_ButtonCharacter || tagType == GFxTag_ButtonSound || tagType == GFxTag_ButtonCharacter2); if (tagType == GFxTag_ButtonCharacter) { // Old button tag. // Read button GFxCharacter records. for (;;) { GFxButtonRecord r; if (r.Read(p, tagType) == false) { // Null record; marks the end of button records. break; } // Search for the depth and insert in the right location. This ensures // that buttons are always drawn correctly. UInt i; for(i=0; i r.ButtonLayer) break; ButtonRecords.InsertAt(i, r); } // Read actions. ButtonActions.Resize(ButtonActions.GetSize() + 1); ButtonActions.Back().Read(p->GetStream(), tagType, (UInt)(p->GetTagEndPosition() - p->Tell())); } else if (tagType == GFxTag_ButtonSound) { #ifndef GFC_NO_SOUND GASSERT(pSound == NULL); // redefinition button sound is error GFxAudioBase* paudio = p->GetLoadStates()->GetAudio(); if (paudio) { GASSERT(paudio->GetSoundTagsReader()); pSound = paudio->GetSoundTagsReader()->ReadButtonSoundDef(p); } else { SkipButtonSoundDef(p); p->LogScriptWarning("GFxButtonCharacterDef::Read: Audio library is not set. Skipping sound definitions."); } #else SkipButtonSoundDef(p); #endif // GFC_NO_SOUND } else if (tagType == GFxTag_ButtonCharacter2) { // Read the menu flag. Menu = p->ReadU8() != 0; int Button2_actionOffset = p->ReadU16(); int NextActionPos = p->Tell() + Button2_actionOffset - 2; // Read button records. for (;;) { GFxButtonRecord r; if (r.Read(p, tagType) == false) { // Null record; marks the end of button records. break; } // Search for the depth and insert in the right location. UInt i; for(i=0; i r.ButtonLayer) break; ButtonRecords.InsertAt(i, r); } if (Button2_actionOffset > 0) { p->SetPosition(NextActionPos); // Read Button2ActionConditions for (;;) { UInt NextActionOffset = p->ReadU16(); NextActionPos = p->Tell() + NextActionOffset - 2; ButtonActions.Resize(ButtonActions.GetSize() + 1); ButtonActions.Back().Read(p->GetStream(), tagType, (NextActionOffset) ? NextActionOffset - 2 : (UInt)(p->GetTagEndPosition() - p->Tell())); if (NextActionOffset == 0 || p->Tell() >= p->GetTagEndPosition()) { // done. break; } // seek to next action. p->SetPosition(NextActionPos); } } } } // Create a mutable instance of our definition. GFxCharacter* GFxButtonCharacterDef::CreateCharacterInstance(GFxASCharacter* parent, GFxResourceId id, GFxMovieDefImpl *pbindingImpl) { GFxCharacter* ch = GHEAP_AUTO_NEW(parent) GFxButtonCharacter(this, pbindingImpl, parent, id); return ch; } void GASButtonObject::commonInit () { } static const GASNameFunction GAS_ButtonFunctionTable[] = { { "getDepth", &GFxASCharacter::CharacterGetDepth }, { 0, 0 } }; GASButtonProto::GASButtonProto(GASStringContext *psc, GASObject* prototype, const GASFunctionRef& constructor) : GASPrototype(psc, prototype, constructor) { InitFunctionMembers(psc, GAS_ButtonFunctionTable); SetMemberRaw(psc, psc->GetBuiltin(GASBuiltin_useHandCursor), GASValue(true), GASPropFlags::PropFlag_DontDelete); SetConstMemberRaw(psc, "blendMode", GASValue::UNSET, GASPropFlags::PropFlag_DontDelete); SetConstMemberRaw(psc, "enabled", GASValue::UNSET, GASPropFlags::PropFlag_DontDelete); SetConstMemberRaw(psc, "scale9Grid", GASValue::UNSET, GASPropFlags::PropFlag_DontDelete); // can't add tabEnabled here, since its presence will stop prototype chain lookup, even if // it is UNSET or "undefined". //SetConstMemberRaw(psc, "tabEnabled", GASValue::UNSET, GASPropFlags::PropFlag_DontDelete); SetConstMemberRaw(psc, "tabIndex", GASValue::UNSET, GASPropFlags::PropFlag_DontDelete); SetConstMemberRaw(psc, "trackAsMenu", GASValue::UNSET, GASPropFlags::PropFlag_DontDelete); } void GASButtonCtorFunction::GlobalCtor(const GASFnCall& fn) { GUNUSED(fn); //fn.Result->SetAsObject(GPtr(*GHEAP_NEW(fn.Env->GetHeap()) GASButtonObject())); } GASObject* GASButtonCtorFunction::CreateNewObject(GASEnvironment* penv) const { return GHEAP_NEW(penv->GetHeap()) GASButtonObject(penv); } GASFunctionRef GASButtonCtorFunction::Register(GASGlobalContext* pgc) { GASStringContext sc(pgc, 8); GASFunctionRef ctor(*GHEAP_NEW(pgc->GetHeap()) GASButtonCtorFunction(&sc)); GPtr proto = *GHEAP_NEW(pgc->GetHeap()) GASButtonProto(&sc, pgc->GetPrototype(GASBuiltin_Object), ctor); pgc->SetPrototype(GASBuiltin_Button, proto); pgc->pGlobal->SetMemberRaw(&sc, pgc->GetBuiltin(GASBuiltin_Button), GASValue(ctor)); return ctor; }