//----------------------------------------------------------------------------- // Copyright (c) 2012 GarageGames, LLC // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. //----------------------------------------------------------------------------- #include "gui/containers/guiAutoScrollCtrl.h" #include "console/consoleTypes.h" #include "console/engineAPI.h" IMPLEMENT_CONOBJECT( GuiAutoScrollCtrl ); ConsoleDocClass( GuiAutoScrollCtrl, "@brief A container that scrolls its child control up over time.\n\n" "This container can be used to scroll a single child control in either of the four directions.\n\n" "@tsexample\n" "// Create a GuiAutoScrollCtrl that scrolls a long text of credits.\n" "new GuiAutoScrollCtrl( CreditsScroller )\n" "{\n" " position = \"0 0\";\n" " extent = Canvas.extent.x SPC Canvas.extent.y;\n" "\n" " scrollDirection = \"Up\"; // Scroll upwards.\n" " startDelay = 4; // Wait 4 seconds before starting to scroll.\n" " isLooping = false; // Don't loop the credits.\n" " scrollOutOfSight = true; // Scroll up fully.\n" "\n" " new GuiMLTextCtrl()\n" " {\n" " text = $CREDITS;\n" " };\n" "};\n" "\n" "function CreditsScroller::onComplete( %this )\n" "{\n" " // Switch back to main menu after credits have rolled.\n" " Canvas.setContent( MainMenu );\n" "}\n" "\n" "// Start rolling credits.\n" "Canvas.setContent( CreditsScroller );\n" "@endtsexample\n\n" "@note Only the first child will be scrolled.\n\n" "@ingroup GuiContainers" ); IMPLEMENT_CALLBACK( GuiAutoScrollCtrl, onTick, void, (), (), "Called every 32ms on the control." ); IMPLEMENT_CALLBACK( GuiAutoScrollCtrl, onStart, void, (), (), "Called when the control starts to scroll." ); IMPLEMENT_CALLBACK( GuiAutoScrollCtrl, onComplete, void, (), (), "Called when the child control has been scrolled in entirety." ); IMPLEMENT_CALLBACK( GuiAutoScrollCtrl, onReset, void, (), (), "Called when the child control is reset to its initial position and the cycle starts again." ); ImplementEnumType( GuiAutoScrollDirection, "Direction in which to scroll the child control.\n\n" "@ingroup GuiContainers" ) { GuiAutoScrollCtrl::Up, "Up", "Scroll from bottom towards top." }, { GuiAutoScrollCtrl::Down, "Down", "Scroll from top towards bottom." }, { GuiAutoScrollCtrl::Left, "Left", "Scroll from right towards left." }, { GuiAutoScrollCtrl::Right, "Right", "Scroll from left towards right." }, EndImplementEnumType; //----------------------------------------------------------------------------- GuiAutoScrollCtrl::GuiAutoScrollCtrl() : mDirection( Up ), mIsLooping( true ), mCurrentPhase( GuiAutoScrollCtrl::PhaseComplete ), mCurrentTime( 0.f ), mCompleteTime(F32_MAX), mCurrentPosition(0.0f), mStartDelay( 3.f ), mResetDelay( 5.f ), mChildBorder( 10 ), mScrollOutOfSight( false ), mScrollSpeed( 1.f ) { mIsContainer = true; } //----------------------------------------------------------------------------- void GuiAutoScrollCtrl::initPersistFields() { addGroup( "Scrolling" ); addField( "scrollDirection", TYPEID< Direction >(), Offset( mDirection, GuiAutoScrollCtrl ), "Direction in which the child control is moved." ); addField( "startDelay", TypeF32, Offset( mStartDelay, GuiAutoScrollCtrl ), "Seconds to wait before starting to scroll." ); addField( "resetDelay", TypeF32, Offset( mResetDelay, GuiAutoScrollCtrl ), "Seconds to wait after scrolling completes before resetting and starting over.\n\n" "@note Only takes effect if #isLooping is true." ); addField( "childBorder", TypeS32, Offset( mChildBorder, GuiAutoScrollCtrl ), "Padding to put around child control (in pixels)." ); addField( "scrollSpeed", TypeF32, Offset( mScrollSpeed, GuiAutoScrollCtrl ), "Scrolling speed in pixels per second." ); addField( "isLooping", TypeBool, Offset( mIsLooping, GuiAutoScrollCtrl ), "If true, the scrolling will reset to the beginning once completing a cycle." ); addField( "scrollOutOfSight", TypeBool, Offset( mScrollOutOfSight, GuiAutoScrollCtrl ), "If true, the child control will be completely scrolled out of sight; otherwise it will only scroll " "until the other end becomes visible." ); endGroup( "Scrolling" ); Parent::initPersistFields(); } //----------------------------------------------------------------------------- bool GuiAutoScrollCtrl::onWake() { if( !Parent::onWake() ) return false; setProcessTicks( true ); return true; } //----------------------------------------------------------------------------- void GuiAutoScrollCtrl::onSleep() { setProcessTicks( false ); Parent::onSleep(); } //----------------------------------------------------------------------------- void GuiAutoScrollCtrl::onChildAdded( GuiControl* control ) { _reset( control ); Parent::onChildAdded( control ); } //----------------------------------------------------------------------------- void GuiAutoScrollCtrl::onChildRemoved( GuiControl* control ) { mCurrentPhase = PhaseComplete; Parent::onChildRemoved( control ); } //----------------------------------------------------------------------------- bool GuiAutoScrollCtrl::_isScrollComplete() const { if( empty() ) return true; GuiControl* control = static_cast< GuiControl* >( at( 0 ) ); U32 axis = _getScrollAxis(); F32 amount = _getScrollAmount(); if( mScrollOutOfSight ) { // If scrolling out of sight, scrolling is complete when the control's rectangle // does not intersect our own rectangle anymore. RectI thisRect( Point2I( 0, 0 ), getExtent() ); return !( thisRect.overlaps( control->getBounds() ) ); } else { if( amount < 0 ) return ( control->getPosition()[ axis ] + control->getExtent()[ axis ] ) < ( getExtent()[ axis ] - mChildBorder ); else return ( control->getPosition()[ axis ] >= mChildBorder ); } } //----------------------------------------------------------------------------- void GuiAutoScrollCtrl::_reset( GuiControl* control ) { U32 axis = _getScrollAxis(); U32 counterAxis = ( axis == 1 ? 0 : 1 ); Point2I newPosition( mChildBorder, mChildBorder ); Point2I newExtent = control->getExtent(); // Fit control on axis that is not scrolled. newExtent[ counterAxis ] = getExtent()[ counterAxis ] - mChildBorder * 2; // For the right and down scrolls, position the control away from the // right/bottom edge of our control. if( mDirection == Right ) newPosition.x = - ( newExtent.x - getExtent().x + mChildBorder ); else if( mDirection == Down ) newPosition.y = - ( newExtent.y - getExtent().y + mChildBorder ); // Set the child geometry. control->setPosition( newPosition ); control->setExtent( newExtent ); // Reset counters. mCurrentTime = 0.0f; mCurrentPhase = PhaseInitial; mCurrentPosition = control->getPosition()[ axis ]; } //----------------------------------------------------------------------------- void GuiAutoScrollCtrl::reset() { if( !empty() ) _reset( static_cast< GuiControl* >( at( 0 ) ) ); } //----------------------------------------------------------------------------- bool GuiAutoScrollCtrl::resize( const Point2I &newPosition, const Point2I &newExtent ) { if( !Parent::resize( newPosition, newExtent ) ) return false; for( iterator i = begin(); i != end(); ++ i ) { GuiControl* control = static_cast< GuiControl* >( *i ); if( control ) _reset( control ); } return true; } //----------------------------------------------------------------------------- void GuiAutoScrollCtrl::childResized( GuiControl* child ) { Parent::childResized( child ); _reset(child); } //----------------------------------------------------------------------------- void GuiAutoScrollCtrl::processTick() { onTick_callback(); } //----------------------------------------------------------------------------- void GuiAutoScrollCtrl::advanceTime( F32 timeDelta ) { if( mCurrentPhase == PhaseComplete ) return; // Wait out initial delay. if( ( mCurrentTime + timeDelta ) < mStartDelay) { mCurrentTime += timeDelta; return; } // Start scrolling if we haven't already. if( mCurrentPhase == PhaseInitial ) { onStart_callback(); mCurrentPhase = PhaseScrolling; } GuiControl* control = static_cast< GuiControl* >( at( 0 ) ); if( !control ) // Should not happen. return; // If not yet complete, scroll some more. if( !_isScrollComplete() ) { U32 axis = _getScrollAxis(); F32 amount = _getScrollAmount(); mCurrentPosition += amount * timeDelta; Point2I newPosition = control->getPosition(); newPosition[ axis ] = mCurrentPosition; control->setPosition( newPosition ); } else { mCurrentTime += timeDelta; if( mCurrentPhase != PhaseComplete && mCurrentPhase != PhaseWait ) { if( mCurrentPhase != PhaseWait ) { onComplete_callback(); mCurrentPhase = PhaseComplete; } mCompleteTime = mCurrentTime; } // Reset, if looping. if( mIsLooping ) { // Wait out reset time and restart. mCurrentPhase = PhaseWait; if( mCurrentTime > ( mCompleteTime + mResetDelay ) ) { onReset_callback(); _reset( control ); } } } } //----------------------------------------------------------------------------- void GuiAutoScrollCtrl::inspectPostApply() { Parent::inspectPostApply(); reset(); } //============================================================================= // API. //============================================================================= // MARK: ---- API ---- //----------------------------------------------------------------------------- DefineEngineMethod( GuiAutoScrollCtrl, reset, void, (),, "Reset scrolling." ) { object->reset(); }