123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657 |
- //-----------------------------------------------------------------------------
- // 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/guiRolloutCtrl.h"
- #include "gui/containers/guiScrollCtrl.h"
- #include "gfx/gfxDrawUtil.h"
- #include "console/engineAPI.h"
- IMPLEMENT_CONOBJECT( GuiRolloutCtrl );
- ConsoleDocClass( GuiRolloutCtrl,
- "@brief A container that shows a single child with an optional header bar that can be used to collapse and expand the rollout.\n\n"
-
- "A rollout is a container that can be collapsed and expanded using smooth animation. By default, rollouts will display a header "
- "with a caption along the top edge of the control which can be clicked by the user to toggle the collapse state of the rollout.\n\n"
-
- "Rollouts will automatically size themselves to exactly fit around their child control. They will also automatically position their child "
- "control in their upper left corner below the header (if present).\n\n"
-
- "@note GuiRolloutCtrls will only work correctly with a single child control. To put multiple controls in a rollout, put them "
- "in their own group using a new GuiControl which then can be put inside the rollout.\n\n"
-
- "@ingroup GuiContainers"
- );
- IMPLEMENT_CALLBACK( GuiRolloutCtrl, onHeaderRightClick, void, (), (),
- "Called when the user right-clicks on the rollout's header. This is useful for implementing "
- "context menus for rollouts." );
- IMPLEMENT_CALLBACK( GuiRolloutCtrl, onExpanded, void, (), (),
- "Called when the rollout is expanded." );
- IMPLEMENT_CALLBACK( GuiRolloutCtrl, onCollapsed, void, (), (),
- "Called when the rollout is collapsed." );
- //-----------------------------------------------------------------------------
- GuiRolloutCtrl::GuiRolloutCtrl()
- : mHeader(0,0,0,0),
- mExpanded(0,0,0,0),
- mChildRect(0,0,0,0),
- mMargin(0,0,0,0)
- {
- mExpanded.set(0,0,200,60);
- mCaption = StringTable->EmptyString();
- mIsExpanded = true;
- mIsAnimating = false;
- mCollapsing = false;
- mAnimateDestHeight = 40;
- mAnimateStep = 1;
- mDefaultHeight = 40;
- mHideHeader = false;
- mMargin.set( 0, 0, 0, 0 );
- mIsContainer = true;
- mCanCollapse = true;
- mAutoCollapseSiblings = false;
- mDefaultCursor = NULL;
- mVertSizingCursor = NULL;
- mHasTexture = false;
- mBitmapBounds = NULL;
- // Make sure we receive our ticks.
- setProcessTicks();
- }
- //-----------------------------------------------------------------------------
- GuiRolloutCtrl::~GuiRolloutCtrl()
- {
- }
- //-----------------------------------------------------------------------------
- void GuiRolloutCtrl::initPersistFields()
- {
- addGroup( "Rollout" );
-
- addField( "caption", TypeRealString, Offset( mCaption, GuiRolloutCtrl ),
- "Text label to display on the rollout header." );
- addField( "margin", TypeRectI, Offset( mMargin, GuiRolloutCtrl ),
- "Margin to put around child control." );
- addField( "defaultHeight", TypeS32, Offset( mDefaultHeight, GuiRolloutCtrl ),
- "Default height of the client area. This is used when no child control has been added to the rollout." );
- addProtectedField( "expanded", TypeBool, Offset( mIsExpanded, GuiRolloutCtrl), &setExpanded, &defaultProtectedGetFn,
- "The current rollout expansion state." );
- addField( "clickCollapse", TypeBool, Offset( mCanCollapse, GuiRolloutCtrl ),
- "Whether the rollout can be collapsed by clicking its header." );
- addField( "hideHeader", TypeBool, Offset( mHideHeader, GuiRolloutCtrl ),
- "Whether to render the rollout header.\n\n"
- "@note If this is false, the user cannot toggle the rollout state with the mouse." );
- addField( "autoCollapseSiblings", TypeBool, Offset( mAutoCollapseSiblings, GuiRolloutCtrl ),
- "Whether to automatically collapse sibling rollouts.\n\n"
- "If this is true, the rollout will automatically collapse all sibling rollout controls when it "
- "is expanded. If this is false, the auto-collapse behavior can be triggered by CTRL (CMD on MAC) "
- "clicking the rollout header. CTRL/CMD clicking also works if this is false, in which case the "
- "auto-collapsing of sibling controls will be temporarily deactivated." );
-
- endGroup( "Rollout" );
- Parent::initPersistFields();
- }
- //=============================================================================
- // Events.
- //=============================================================================
- // MARK: ---- Events ----
- //-----------------------------------------------------------------------------
- bool GuiRolloutCtrl::onAdd()
- {
- if ( !Parent::onAdd() )
- return false;
- mHasTexture = ( mProfile ? mProfile->constructBitmapArray() > 0 : false );
- if ( mHasTexture )
- mBitmapBounds = mProfile->mBitmapArrayRects.address();
- // Calculate Heights for this control
- calculateHeights();
- return true;
- }
- //-----------------------------------------------------------------------------
- bool GuiRolloutCtrl::onWake()
- {
- if (! Parent::onWake())
- return false;
- if( !mIsAnimating && mIsExpanded )
- sizeToContents();
- return true;
- }
- //-----------------------------------------------------------------------------
- void GuiRolloutCtrl::addObject( SimObject *obj )
- {
- // Call Parent.
- Parent::addObject( obj );
- sizeToContents();
- }
- //-----------------------------------------------------------------------------
- void GuiRolloutCtrl::removeObject( SimObject *obj )
- {
- // Call Parent.
- Parent::removeObject( obj );
- // Recalculate our rectangles.
- calculateHeights();
- }
- //-----------------------------------------------------------------------------
- void GuiRolloutCtrl::onMouseDown( const GuiEvent &event )
- {
- mouseLock();
- }
- //-----------------------------------------------------------------------------
- bool GuiRolloutCtrl::_onMouseUp( const GuiEvent &event, bool lockedMouse )
- {
- Point2I localPoint = globalToLocalCoord( event.mousePoint );
- if( mCanCollapse && mHeader.pointInRect( localPoint ) && !mIsAnimating && ( !lockedMouse || isMouseLocked() ) )
- {
- // If Ctrl/Cmd-clicking a header, collapse all sibling GuiRolloutCtrls.
-
- if( (( mAutoCollapseSiblings && !mIsExpanded && !( event.modifier & SI_PRIMARY_CTRL ))
- || ( !mAutoCollapseSiblings && event.modifier & SI_PRIMARY_CTRL ) ) )
- {
- for( SimSet::iterator iter = getParent()->begin(); iter != getParent()->end(); ++ iter )
- {
- GuiRolloutCtrl* ctrl = dynamic_cast< GuiRolloutCtrl* >( *iter );
- if( ctrl && ctrl != this && ctrl->mCanCollapse )
- ctrl->instantCollapse();
- }
-
- if( !mIsExpanded )
- expand();
- }
- else
- {
- // Toggle expansion.
-
- toggleExpanded( false );
- }
-
- return true;
- }
-
- return false;
- }
- //-----------------------------------------------------------------------------
- void GuiRolloutCtrl::onMouseUp( const GuiEvent &event )
- {
- _onMouseUp( event, true );
- if( isMouseLocked() )
- mouseUnlock();
- }
- //-----------------------------------------------------------------------------
- void GuiRolloutCtrl::onRightMouseUp( const GuiEvent& event )
- {
- Parent::onRightMouseUp( event );
-
- Point2I localMouse = globalToLocalCoord( event.mousePoint );
- if( mHeader.pointInRect( localMouse ) )
- onHeaderRightClick_callback();
- }
- //-----------------------------------------------------------------------------
- bool GuiRolloutCtrl::onMouseUpEditor( const GuiEvent& event, Point2I offset )
- {
- return ( event.modifier & SI_PRIMARY_ALT && _onMouseUp( event, false ) );
- }
- //=============================================================================
- // Sizing.
- //=============================================================================
- // MARK: ---- Sizing ----
- //-----------------------------------------------------------------------------
- void GuiRolloutCtrl::calculateHeights()
- {
- S32 barHeight = 20;
- if ( mHasTexture && mProfile && mProfile->mBitmapArrayRects.size() >= NumBitmaps )
- {
- // Store Header Rectangle
- mHeader.set( 0, 0, getWidth(), mProfile->mBitmapArrayRects[ CollapsedCenter ].extent.y );
- // Bottom Bar Max
- barHeight = mProfile->mBitmapArrayRects[ TopLeftHeader ].extent.y;
- }
- else
- {
- mHeader.set( 0, 0, getWidth(), barHeight );
- }
-
- if ( mHideHeader )
- {
- barHeight = 0;
- mHeader.extent.y = 0;
- }
- GuiControl *content = static_cast<GuiControl*>( at(0) );
- if ( content != NULL )
- mExpanded.set( 0, 0, getWidth(), barHeight + content->getHeight() + ( mMargin.point.y + mMargin.extent.y ) );
- else
- mExpanded.set( 0, 0, getWidth(), barHeight + mDefaultHeight );
- }
- //-----------------------------------------------------------------------------
- bool GuiRolloutCtrl::resize( const Point2I &newPosition, const Point2I &newExtent )
- {
- if ( !Parent::resize( newPosition, newExtent ) )
- return false;
- // Recalculate Heights and resize ourself appropriately.
- calculateHeights();
- GuiControl *content = dynamic_cast<GuiControl*>( at(0) );
-
- // Size Content Properly?!
- if ( mNotifyChildrenResized && content != NULL )
- {
- S32 barHeight = ( mHideHeader ) ? 0 : 20;
- if( !mHideHeader && mHasTexture && mProfile && mProfile->mBitmapArrayRects.size() >= NumBitmaps )
- {
- barHeight = mProfile->mBitmapArrayRects[ TopLeftHeader ].extent.y;
- }
- mChildRect.set( mMargin.point.x,
- mHeader.extent.y + mMargin.point.y,
- getWidth() - ( mMargin.point.x + mMargin.extent.x ),
- getHeight() - ( barHeight + ( mMargin.point.y + mMargin.extent.y ) ) );
- if ( content->resize( mChildRect.point, mChildRect.extent ) )
- return true;
- }
- // Nothing sized
- return false;
- }
- //-----------------------------------------------------------------------------
- void GuiRolloutCtrl::sizeToContents()
- {
- calculateHeights();
- // Set destination height
- if ( size() > 0 )
- instantExpand();
- else
- instantCollapse();
- }
- //-----------------------------------------------------------------------------
- void GuiRolloutCtrl::instantExpand()
- {
- mAnimateDestHeight = mExpanded.extent.y;
- mCollapsing = false;
- mIsExpanded = true;
- mIsAnimating = false;
- resize( getPosition() + mExpanded.point, mExpanded.extent );
- onExpanded_callback();
- }
- //-----------------------------------------------------------------------------
- void GuiRolloutCtrl::instantCollapse()
- {
- mAnimateDestHeight = mHeader.extent.y;
- mCollapsing = false;
- mIsExpanded = false;
- mIsAnimating = false;
- resize( getPosition() + mHeader.point, mHeader.extent );
- onCollapsed_callback();
- }
- //-----------------------------------------------------------------------------
- void GuiRolloutCtrl::toggleExpanded( bool instant )
- {
- if ( mIsExpanded )
- {
- if ( instant )
- instantCollapse();
- else
- collapse();
- }
- else
- {
- if ( instant )
- instantExpand();
- else
- expand();
- }
- }
- //-----------------------------------------------------------------------------
- void GuiRolloutCtrl::childResized( GuiControl *child )
- {
- Parent::childResized( child );
- calculateHeights();
- // While we are animating we are constantly resizing our children
- // and therefore need to ignore this call to 'instantExpand' which would
- // halt the animation in some crappy intermediate stage.
- if ( mIsExpanded && !mIsAnimating )
- {
- mNotifyChildrenResized = false;
- instantExpand();
- mNotifyChildrenResized = true;
- }
- }
- //=============================================================================
- // Animation.
- //=============================================================================
- // MARK: ---- Animation ----
- //-----------------------------------------------------------------------------
- void GuiRolloutCtrl::animateTo( S32 height )
- {
- // We do nothing if we're already animating
- if( mIsAnimating )
- return;
- bool collapsing = (bool)( getHeight() > height );
- // If we're already at the destination height, bail
- if ( getHeight() >= height && !collapsing )
- {
- mIsExpanded = true;
- return;
- }
- // If we're already at the destination height, bail
- if ( getHeight() <= height && collapsing )
- {
- mIsExpanded = false;
- return;
- }
- // Set destination height
- mAnimateDestHeight = height;
- // Set Animation Mode
- mCollapsing = collapsing;
- // Set Animation Step (Increment)
- if ( collapsing )
- mAnimateStep = (S32)mFloor( (F32)( getHeight() - height ) / 3.f );
- else
- mAnimateStep = (S32)mFloor( (F32)( height - getHeight() ) / 3.f );
- // Start our animation
- mIsAnimating = true;
- }
- //-----------------------------------------------------------------------------
- void GuiRolloutCtrl::processTick()
- {
- // We do nothing here if we're NOT animating
- if ( !mIsAnimating )
- return;
- // Sanity check to fix non collapsing panels.
- if ( mAnimateStep == 0 )
- mAnimateStep = 1;
- S32 newHeight = getHeight();
- // We're collapsing ourself down (Hiding our contents)
- if( mCollapsing )
- {
- if ( newHeight < mAnimateDestHeight )
- newHeight = mAnimateDestHeight;
- else if ( ( newHeight - mAnimateStep ) < mAnimateDestHeight )
- newHeight = mAnimateDestHeight;
- if ( newHeight == mAnimateDestHeight )
- mIsAnimating = false;
- else
- newHeight -= mAnimateStep;
- if( !mIsAnimating )
- {
- mIsExpanded = false;
- }
- }
- else // We're expanding ourself (Showing our contents)
- {
- if ( newHeight > mAnimateDestHeight )
- newHeight = mAnimateDestHeight;
- else if ( ( newHeight + mAnimateStep ) > mAnimateDestHeight )
- newHeight = mAnimateDestHeight;
- if ( newHeight == mAnimateDestHeight )
- mIsAnimating = false;
- else
- newHeight += mAnimateStep;
- if ( !mIsAnimating )
- mIsExpanded = true;
- }
- if ( newHeight != getHeight() )
- setHeight( newHeight );
- if ( !mIsAnimating )
- {
- if( mCollapsing )
- onCollapsed_callback();
- else if( !mCollapsing )
- onExpanded_callback();
- calculateHeights();
- }
- GuiControl* parent = getParent();
- if ( parent )
- {
- parent->childResized( this );
- // if our parent's parent is a scroll control, scrollvisible.
- GuiScrollCtrl* scroll = dynamic_cast<GuiScrollCtrl*>( parent->getParent() );
- if ( scroll )
- {
- scroll->scrollRectVisible( getBounds() );
- }
- }
- }
- //=============================================================================
- // Rendering.
- //=============================================================================
- // MARK: ---- Rendering ----
- //-----------------------------------------------------------------------------
- void GuiRolloutCtrl::onRender( Point2I offset, const RectI &updateRect )
- {
- if( !mProfile || mProfile->mFont == NULL )
- return;
- // Calculate actual world bounds for rendering
- RectI worldBounds( offset, getExtent() );
- // if opaque, fill the update rect with the fill color
- if ( mProfile->mOpaque )
- GFX->getDrawUtil()->drawRectFill( worldBounds, mProfile->mFillColor );
- if ( mProfile->mBitmapArrayRects.size() >= NumBitmaps )
- {
- GFX->getDrawUtil()->clearBitmapModulation();
- // Draw Rollout From Skin
- if ( !mIsExpanded && !mIsAnimating )
- renderFixedBitmapBordersFilled( worldBounds, 1, mProfile );
- else if ( mHideHeader )
- renderSizableBitmapBordersFilledIndex( worldBounds, MidPageLeft, mProfile );
- else
- renderSizableBitmapBordersFilledIndex( worldBounds, TopLeftHeader, mProfile );
- }
- if ( !(mIsExpanded && mHideHeader ) )
- {
- // Draw Caption ( Vertically Centered )
- ColorI currColor;
- GFX->getDrawUtil()->getBitmapModulation( &currColor );
- Point2I textPosition = mHeader.point + offset + mProfile->mTextOffset;
- GFX->getDrawUtil()->setBitmapModulation( mProfile->mFontColor );
- renderJustifiedText( textPosition, mHeader.extent, mCaption );
- GFX->getDrawUtil()->setBitmapModulation( currColor );
- }
- // If we're collapsed we contain the first child as our content
- // thus we don't render it when collapsed. but to support modified
- // rollouts with custom header buttons etc we still render our other
- // children. -JDD
- GuiControl *pChild = dynamic_cast<GuiControl*>( at(0) );
- if ( pChild )
- {
- if ( !mIsExpanded && !mIsAnimating && pChild->isVisible() )
- {
- pChild->setVisible( false );
- }
- else if ( (mIsExpanded || mIsAnimating) && !pChild->isVisible() )
- {
- pChild->setVisible( true );
- }
- }
- renderChildControls( offset, updateRect );
- // Render our border should we have it specified in our profile.
- renderBorder(worldBounds, mProfile);
- }
- //=============================================================================
- // Console Methods.
- //=============================================================================
- // MARK: ---- Console Methods ----
- //-----------------------------------------------------------------------------
- DefineEngineMethod( GuiRolloutCtrl, isExpanded, bool, (),,
- "Determine whether the rollout is currently expanded, i.e. whether the child control is visible.\n\n"
- "@return True if the rollout is expanded, false if not." )
- {
- return object->isExpanded();
- }
- //-----------------------------------------------------------------------------
- DefineEngineMethod( GuiRolloutCtrl, collapse, void, (),,
- "Collapse the rollout if it is currently expanded. This will make the rollout's child control invisible.\n\n"
- "@note The rollout will animate to collapsed state. To instantly collapse without animation, use instantCollapse()." )
- {
- object->collapse();
- }
- //-----------------------------------------------------------------------------
- DefineEngineMethod( GuiRolloutCtrl, expand, void, (),,
- "Expand the rollout if it is currently collapsed. This will make the rollout's child control visible.\n\n"
- "@note The rollout will animate to expanded state. To instantly expand without animation, use instantExpand()." )
- {
- object->expand();
- }
- //-----------------------------------------------------------------------------
- DefineEngineMethod( GuiRolloutCtrl, toggleCollapse, void, (),,
- "Toggle the current collapse state of the rollout. If it is currently expanded, then collapse it. If it "
- "is currently collapsed, then expand it." )
- {
- if( object->isExpanded() )
- object->collapse();
- else
- object->expand();
- }
- //-----------------------------------------------------------------------------
- DefineEngineMethod( GuiRolloutCtrl, toggleExpanded, void, ( bool instantly ), ( false ),
- "Toggle the current expansion state of the rollout If it is currently expanded, then collapse it. If it "
- "is currently collapsed, then expand it.\n\n"
- "@param instant If true, the rollout will toggle its state without animation. Otherwise, the rollout will "
- "smoothly slide into the opposite state." )
- {
- object->toggleExpanded( instantly );
- }
- //-----------------------------------------------------------------------------
- DefineEngineMethod( GuiRolloutCtrl, instantCollapse, void, (),,
- "Instantly collapse the rollout without animation. To smoothly slide the rollout to collapsed state, use collapse()." )
- {
- object->instantCollapse();
- }
- //-----------------------------------------------------------------------------
- DefineEngineMethod( GuiRolloutCtrl, instantExpand, void, (),,
- "Instantly expand the rollout without animation. To smoothly slide the rollout to expanded state, use expand()." )
- {
- object->instantExpand();
- }
- //-----------------------------------------------------------------------------
- DefineEngineMethod( GuiRolloutCtrl, sizeToContents, void, (),,
- "Resize the rollout to exactly fit around its child control. This can be used to manually trigger a recomputation of "
- "the rollout size." )
- {
- object->sizeToContents();
- }
|