mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-01-20 04:34:48 +00:00
1015 lines
28 KiB
C++
1015 lines
28 KiB
C++
//-----------------------------------------------------------------------------
|
|
// 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/guiTabBookCtrl.h"
|
|
#include "console/engineAPI.h"
|
|
#include "gui/core/guiCanvas.h"
|
|
#include "gui/editor/guiEditCtrl.h"
|
|
#include "gui/controls/guiPopUpCtrl.h"
|
|
#include "gui/core/guiDefaultControlRender.h"
|
|
#include "gfx/gfxDrawUtil.h"
|
|
#include "console/typeValidators.h"
|
|
|
|
|
|
IMPLEMENT_CONOBJECT( GuiTabBookCtrl );
|
|
|
|
ConsoleDocClass( GuiTabBookCtrl,
|
|
"@brief A container \n\n"
|
|
|
|
"@tsexample\n"
|
|
"// Create \n"
|
|
"@endtsexample\n\n"
|
|
|
|
"@note Only GuiTabPageCtrls must be added to GuiTabBookCtrls. If an object of a different "
|
|
"class is added to the control, it will be reassigned to either the active page or the "
|
|
"tab book's parent.\n\n"
|
|
|
|
"@see GuiTabPageCtrl\n"
|
|
|
|
"@ingroup GuiContainers"
|
|
);
|
|
|
|
ImplementEnumType( GuiTabPosition,
|
|
"Where the control should put the tab headers for selecting individual pages.\n\n"
|
|
"@ingroup GuiContainers" )
|
|
{ GuiTabBookCtrl::AlignTop, "Top", "Tab headers on top edge." },
|
|
{ GuiTabBookCtrl::AlignBottom,"Bottom", "Tab headers on bottom edge." }
|
|
EndImplementEnumType;
|
|
|
|
IMPLEMENT_CALLBACK( GuiTabBookCtrl, onTabSelected, void, ( const String& text, U32 index ), ( text, index ),
|
|
"Called when a new tab page is selected.\n\n"
|
|
"@param text Text of the page header for the tab that is being selected.\n"
|
|
"@param index Index of the tab page being selected." );
|
|
IMPLEMENT_CALLBACK(GuiTabBookCtrl, onTabUnSelected, void, (const String& text, U32 index), (text, index),
|
|
"Called when a new tab page is unselected.\n\n"
|
|
"@param text Text of the page header for the tab that is being unselected.\n"
|
|
"@param index Index of the tab page being unselected.");
|
|
IMPLEMENT_CALLBACK( GuiTabBookCtrl, onTabRightClick, void, ( const String& text, U32 index ), ( text, index ),
|
|
"Called when the user right-clicks on a tab page header.\n\n"
|
|
"@param text Text of the page header for the tab that is being selected.\n"
|
|
"@param index Index of the tab page being selected." );
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
GuiTabBookCtrl::GuiTabBookCtrl()
|
|
{
|
|
VECTOR_SET_ASSOCIATION( mPages );
|
|
|
|
mTabHeight = 24;
|
|
mTabPosition = AlignTop;
|
|
mActivePage = NULL;
|
|
mHoverTab = NULL;
|
|
mHasTexture = false;
|
|
mBitmapBounds = NULL;
|
|
setExtent( 400, 300 );
|
|
mPageRect = RectI(0,0,0,0);
|
|
mTabRect = RectI(0,0,0,0);
|
|
mFrontTabPadding = 0;
|
|
|
|
mPages.reserve(12);
|
|
mTabMargin = 7;
|
|
mMinTabWidth = 64;
|
|
mIsContainer = true;
|
|
mSelectedPageNum = -1;
|
|
mDefaultPageNum = -1;
|
|
|
|
mAllowReorder = false;
|
|
mDraggingTab = false;
|
|
mDraggingTabRect = false;
|
|
mIsFirstWake = true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void GuiTabBookCtrl::initPersistFields()
|
|
{
|
|
docsURL;
|
|
addGroup( "TabBook" );
|
|
|
|
addField( "tabPosition", TYPEID< TabPosition >(), Offset( mTabPosition, GuiTabBookCtrl ),
|
|
"Where to place the tab page headers." );
|
|
addFieldV( "tabMargin", TypeRangedS32, Offset( mTabMargin, GuiTabBookCtrl ), &CommonValidators::PositiveInt,
|
|
"Spacing to put between individual tab page headers." );
|
|
addFieldV( "minTabWidth", TypeRangedS32, Offset( mMinTabWidth, GuiTabBookCtrl ), &CommonValidators::PositiveInt,
|
|
"Minimum width allocated to a tab page header." );
|
|
addFieldV( "tabHeight", TypeRangedS32, Offset( mTabHeight, GuiTabBookCtrl ), &CommonValidators::PositiveInt,
|
|
"Height of tab page headers." );
|
|
addField( "allowReorder", TypeBool, Offset( mAllowReorder, GuiTabBookCtrl ),
|
|
"Whether reordering tabs with the mouse is allowed." );
|
|
addFieldV( "defaultPage", TypeRangedS32, Offset( mDefaultPageNum, GuiTabBookCtrl ), &CommonValidators::NegDefaultInt,
|
|
"Index of page to select on first onWake() call (-1 to disable)." );
|
|
|
|
addProtectedFieldV( "selectedPage", TypeRangedS32, Offset( mSelectedPageNum, GuiTabBookCtrl ),
|
|
&_setSelectedPage, &defaultProtectedGetFn, &CommonValidators::PositiveInt,
|
|
"Index of currently selected page." );
|
|
|
|
addField( "frontTabPadding", TypeS32, Offset( mFrontTabPadding, GuiTabBookCtrl ),
|
|
"X offset of first tab page header." );
|
|
|
|
endGroup( "TabBook" );
|
|
|
|
Parent::initPersistFields();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void GuiTabBookCtrl::onChildRemoved( GuiControl* child )
|
|
{
|
|
for (S32 i = 0; i < mPages.size(); i++ )
|
|
{
|
|
GuiTabPageCtrl* tab = mPages[i].Page;
|
|
if( tab == child )
|
|
{
|
|
mPages.erase( i );
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Calculate Page Information
|
|
calculatePageTabs();
|
|
|
|
// Active Index.
|
|
mSelectedPageNum = getMin( mSelectedPageNum, mPages.size() - 1 );
|
|
|
|
if ( mSelectedPageNum != -1 )
|
|
{
|
|
// Select Page.
|
|
selectPage( mSelectedPageNum );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void GuiTabBookCtrl::onChildAdded( GuiControl *child )
|
|
{
|
|
GuiTabPageCtrl *page = dynamic_cast<GuiTabPageCtrl*>(child);
|
|
if( !page )
|
|
{
|
|
Con::warnf("GuiTabBookCtrl::onChildAdded - attempting to add NON GuiTabPageCtrl as child page");
|
|
SimObject *simObj = reinterpret_cast<SimObject*>(child);
|
|
removeObject( simObj );
|
|
if( mActivePage )
|
|
{
|
|
mActivePage->addObject( simObj );
|
|
}
|
|
else
|
|
{
|
|
Con::warnf("GuiTabBookCtrl::onChildAdded - unable to find active page to reassign ownership of new child control to, placing on parent");
|
|
GuiControl *rent = getParent();
|
|
if( rent )
|
|
rent->addObject( simObj );
|
|
}
|
|
return;
|
|
}
|
|
|
|
TabHeaderInfo newPage;
|
|
|
|
newPage.Page = page;
|
|
newPage.TabRow = -1;
|
|
newPage.TabColumn = -1;
|
|
|
|
mPages.push_back( newPage );
|
|
|
|
// Calculate Page Information
|
|
calculatePageTabs();
|
|
|
|
if( page->getFitBook() )
|
|
fitPage( page );
|
|
|
|
// Select this Page
|
|
selectPage( page );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool GuiTabBookCtrl::reOrder(SimObject* obj, SimObject* target)
|
|
{
|
|
if ( !Parent::reOrder(obj, target) )
|
|
return false;
|
|
|
|
// Store the Selected Page.
|
|
GuiTabPageCtrl *selectedPage = NULL;
|
|
if ( mSelectedPageNum != -1 )
|
|
selectedPage = mPages[mSelectedPageNum].Page;
|
|
|
|
// Determine the Target Page Index.
|
|
S32 targetIndex = -1;
|
|
for( S32 i = 0; i < mPages.size(); i++ )
|
|
{
|
|
const TabHeaderInfo &info = mPages[i];
|
|
if ( info.Page == target )
|
|
{
|
|
targetIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( targetIndex == -1 )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for( S32 i = 0; i < mPages.size(); i++ )
|
|
{
|
|
const TabHeaderInfo &info = mPages[i];
|
|
if ( info.Page == obj )
|
|
{
|
|
// Store Info.
|
|
TabHeaderInfo objPage = info;
|
|
|
|
// Remove.
|
|
mPages.erase( i );
|
|
// Insert.
|
|
mPages.insert( targetIndex, objPage );
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Update Tabs.
|
|
calculatePageTabs();
|
|
|
|
// Reselect Page.
|
|
selectPage( selectedPage );
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool GuiTabBookCtrl::acceptsAsChild( SimObject* object ) const
|
|
{
|
|
// Only accept tab pages.
|
|
return ( dynamic_cast< GuiTabPageCtrl* >( object ) != NULL );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool GuiTabBookCtrl::onWake()
|
|
{
|
|
if (! Parent::onWake())
|
|
return false;
|
|
|
|
mHasTexture = mProfile->constructBitmapArray() > 0;
|
|
if( mHasTexture )
|
|
{
|
|
mBitmapBounds = mProfile->mBitmapArrayRects.address();
|
|
mTabHeight = mBitmapBounds[TabSelected].extent.y;
|
|
}
|
|
|
|
calculatePageTabs();
|
|
|
|
if( mIsFirstWake )
|
|
{
|
|
// Awaken all pages, visible or not. We need to do this so
|
|
// any pages that make use of a language table for their label
|
|
// are correctly initialized.
|
|
for ( U32 i = 0; i < mPages.size(); ++i)
|
|
{
|
|
if ( !mPages[i].Page->isAwake() )
|
|
{
|
|
mPages[i].Page->awaken();
|
|
}
|
|
}
|
|
|
|
if( mDefaultPageNum >= 0 && mDefaultPageNum < mPages.size() )
|
|
selectPage( mDefaultPageNum );
|
|
|
|
mIsFirstWake = false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void GuiTabBookCtrl::addNewPage( const char* text )
|
|
{
|
|
GuiTabPageCtrl* page = new GuiTabPageCtrl();
|
|
|
|
if( text )
|
|
page->setText( text );
|
|
|
|
page->registerObject();
|
|
addObject( page );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool GuiTabBookCtrl::resize(const Point2I &newPosition, const Point2I &newExtent)
|
|
{
|
|
bool result = Parent::resize( newPosition, newExtent );
|
|
|
|
calculatePageTabs();
|
|
|
|
for (S32 i = 0; i < mPages.size(); i++)
|
|
{
|
|
const TabHeaderInfo& info = mPages[i];
|
|
GuiTabPageCtrl* page = info.Page;
|
|
|
|
if(page->getFitBook())
|
|
fitPage(page);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void GuiTabBookCtrl::childResized(GuiControl *child)
|
|
{
|
|
Parent::childResized( child );
|
|
|
|
//child->resize( mPageRect.point, mPageRect.extent );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void GuiTabBookCtrl::onMouseDown(const GuiEvent &event)
|
|
{
|
|
mDraggingTab = false;
|
|
mDraggingTabRect = false;
|
|
Point2I localMouse = globalToLocalCoord( event.mousePoint );
|
|
if( mTabRect.pointInRect( localMouse ) )
|
|
{
|
|
GuiTabPageCtrl *tab = findHitTab( localMouse );
|
|
if( tab != NULL )
|
|
{
|
|
selectPage( tab );
|
|
mDraggingTab = mAllowReorder;
|
|
}
|
|
else
|
|
{
|
|
mDraggingTabRect = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void GuiTabBookCtrl::onMouseUp(const GuiEvent &event)
|
|
{
|
|
Parent::onMouseUp( event );
|
|
|
|
mDraggingTab = false;
|
|
mDraggingTabRect = false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void GuiTabBookCtrl::onMouseDragged(const GuiEvent &event)
|
|
{
|
|
Parent::onMouseDragged( event );
|
|
|
|
if ( !mDraggingTab )
|
|
return;
|
|
|
|
GuiTabPageCtrl *selectedPage = NULL;
|
|
if ( mSelectedPageNum != -1 )
|
|
selectedPage = mPages[mSelectedPageNum].Page;
|
|
|
|
if ( !selectedPage )
|
|
return;
|
|
|
|
Point2I localMouse = globalToLocalCoord( event.mousePoint );
|
|
if( mTabRect.pointInRect( localMouse ) )
|
|
{
|
|
GuiTabPageCtrl *tab = findHitTab( localMouse );
|
|
if( tab != NULL && tab != selectedPage )
|
|
{
|
|
S32 targetIndex = -1;
|
|
for( S32 i = 0; i < mPages.size(); i++ )
|
|
{
|
|
if( mPages[i].Page == tab )
|
|
{
|
|
targetIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( targetIndex > mSelectedPageNum )
|
|
{
|
|
reOrder( tab, selectedPage );
|
|
}
|
|
else
|
|
{
|
|
reOrder( selectedPage, tab );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void GuiTabBookCtrl::onMouseMove(const GuiEvent &event)
|
|
{
|
|
Point2I localMouse = globalToLocalCoord( event.mousePoint );
|
|
if( mTabRect.pointInRect( localMouse ) )
|
|
{
|
|
GuiTabPageCtrl *tab = findHitTab( localMouse );
|
|
if( tab != NULL && mHoverTab != tab )
|
|
mHoverTab = tab;
|
|
else if ( !tab )
|
|
mHoverTab = NULL;
|
|
}
|
|
Parent::onMouseMove( event );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void GuiTabBookCtrl::onMouseLeave( const GuiEvent &event )
|
|
{
|
|
Parent::onMouseLeave( event );
|
|
|
|
if(mDraggingTab)
|
|
{
|
|
//we dragged the tab out, so do something about that
|
|
GuiTabPageCtrl* selectedPage = NULL;
|
|
if (mSelectedPageNum != -1)
|
|
selectedPage = mPages[mSelectedPageNum].Page;
|
|
|
|
mDraggingTab = false;
|
|
|
|
Con::executef(this, "onTabDraggedOut", selectedPage->getIdString());
|
|
}
|
|
|
|
mHoverTab = NULL;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool GuiTabBookCtrl::onMouseDownEditor(const GuiEvent &event, Point2I offset)
|
|
{
|
|
bool handled = false;
|
|
Point2I localMouse = globalToLocalCoord( event.mousePoint );
|
|
|
|
if( mTabRect.pointInRect( localMouse ) )
|
|
{
|
|
GuiTabPageCtrl *tab = findHitTab( localMouse );
|
|
if( tab != NULL )
|
|
{
|
|
selectPage( tab );
|
|
handled = true;
|
|
}
|
|
}
|
|
|
|
#ifdef TORQUE_TOOLS
|
|
// This shouldn't be called if it's not design time, but check just incase
|
|
if ( GuiControl::smDesignTime )
|
|
{
|
|
// If we clicked in the editor and our addset is the tab book
|
|
// ctrl, select the child ctrl so we can edit it's properties
|
|
GuiEditCtrl* edit = GuiControl::smEditorHandle;
|
|
if( edit && ( edit->getAddSet() == this ) && mActivePage != NULL )
|
|
edit->select( mActivePage );
|
|
}
|
|
#endif
|
|
|
|
// Return whether we handled this or not.
|
|
return handled;
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void GuiTabBookCtrl::onRightMouseUp( const GuiEvent& event )
|
|
{
|
|
Point2I localMouse = globalToLocalCoord( event.mousePoint );
|
|
if( mTabRect.pointInRect( localMouse ) )
|
|
{
|
|
GuiTabPageCtrl* tab = findHitTab( localMouse );
|
|
if( tab )
|
|
onTabRightClick_callback( tab->getText(), getPageNum( tab ) );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void GuiTabBookCtrl::onRender(Point2I offset, const RectI &updateRect)
|
|
{
|
|
RectI tabRect = mTabRect;
|
|
tabRect.point += offset;
|
|
RectI pageRect = mPageRect;
|
|
pageRect.point += offset;
|
|
|
|
// We're so nice we'll store the old modulation before we clear it for our rendering! :)
|
|
ColorI oldModulation;
|
|
GFX->getDrawUtil()->getBitmapModulation( &oldModulation );
|
|
|
|
// Wipe it out
|
|
GFX->getDrawUtil()->clearBitmapModulation();
|
|
|
|
Parent::onRender(offset, updateRect);
|
|
|
|
// Clip to tab area
|
|
RectI savedClipRect = GFX->getClipRect();
|
|
RectI clippedTabRect = tabRect;
|
|
clippedTabRect.intersect( savedClipRect );
|
|
GFX->setClipRect( clippedTabRect );
|
|
|
|
// Render our tabs
|
|
renderTabs( offset, tabRect );
|
|
|
|
// Restore Rect.
|
|
GFX->setClipRect( savedClipRect );
|
|
|
|
// Restore old modulation
|
|
GFX->getDrawUtil()->setBitmapModulation( oldModulation );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void GuiTabBookCtrl::renderTabs( const Point2I &offset, const RectI &tabRect )
|
|
{
|
|
// If the tab size is zero, don't render tabs,
|
|
// assuming it's a tab-less book
|
|
if( mPages.empty() || mTabHeight <= 0 )
|
|
return;
|
|
|
|
for( S32 i = 0; i < mPages.size(); i++ )
|
|
{
|
|
const TabHeaderInfo ¤tTabInfo = mPages[i];
|
|
RectI tabBounds = mPages[i].TabRect;
|
|
tabBounds.point += offset;
|
|
GuiTabPageCtrl *tab = mPages[i].Page;
|
|
if( tab != NULL )
|
|
renderTab( tabBounds, tab );
|
|
|
|
// If we're on the last tab, draw the nice end piece
|
|
if( i + 1 == mPages.size() )
|
|
{
|
|
Point2I tabEndPoint = Point2I(currentTabInfo.TabRect.point.x + currentTabInfo.TabRect.extent.x + offset.x, currentTabInfo.TabRect.point.y + offset.y);
|
|
Point2I tabEndExtent = Point2I((tabRect.point.x + tabRect.extent.x) - tabEndPoint.x, currentTabInfo.TabRect.extent.y);
|
|
RectI tabEndRect = RectI(tabEndPoint,tabEndExtent);
|
|
|
|
GFX->setClipRect( tabEndRect );
|
|
|
|
// As it turns out the last tab can be outside the viewport in which
|
|
// case trying to render causes a DX assert. Could be better if
|
|
// setClipRect returned a bool.
|
|
if ( GFX->getViewport().isValidRect() )
|
|
renderFixedBitmapBordersFilled( tabEndRect, TabEnds + 1, mProfile );
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void GuiTabBookCtrl::renderTab(const RectI& tabRect, GuiTabPageCtrl *tab)
|
|
{
|
|
StringTableEntry text = tab->getText();
|
|
ColorI oldColor;
|
|
|
|
GFX->getDrawUtil()->getBitmapModulation( &oldColor );
|
|
|
|
// Is this a skinned control?
|
|
if( mHasTexture && mProfile->mBitmapArrayRects.size() >= 9 )
|
|
{
|
|
S32 indexMultiplier = 1;
|
|
switch( mTabPosition )
|
|
{
|
|
case AlignTop:
|
|
case AlignBottom:
|
|
|
|
if ( mActivePage == tab )
|
|
indexMultiplier += TabSelected;
|
|
else if( mHoverTab == tab )
|
|
indexMultiplier += TabHover;
|
|
else
|
|
indexMultiplier += TabNormal;
|
|
break;
|
|
}
|
|
|
|
renderFixedBitmapBordersFilled( tabRect, indexMultiplier, mProfile );
|
|
}
|
|
else
|
|
{
|
|
// If this isn't a skinned control or the bitmap is simply missing, handle it WELL
|
|
if ( mActivePage == tab )
|
|
GFX->getDrawUtil()->drawRectFill(tabRect, mProfile->mFillColor);
|
|
else if( mHoverTab == tab )
|
|
GFX->getDrawUtil()->drawRectFill(tabRect, mProfile->mFillColorHL);
|
|
else
|
|
GFX->getDrawUtil()->drawRectFill(tabRect, mProfile->mFillColorNA);
|
|
|
|
}
|
|
|
|
|
|
GFX->getDrawUtil()->setBitmapModulation(mProfile->mFontColor);
|
|
|
|
switch( mTabPosition )
|
|
{
|
|
case AlignTop:
|
|
case AlignBottom:
|
|
renderJustifiedText( tabRect.point, tabRect.extent, text);
|
|
break;
|
|
}
|
|
|
|
GFX->getDrawUtil()->setBitmapModulation( oldColor);
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void GuiTabBookCtrl::setUpdate()
|
|
{
|
|
Parent::setUpdate();
|
|
|
|
setUpdateRegion(Point2I(0,0), getExtent());
|
|
|
|
calculatePageTabs();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
S32 GuiTabBookCtrl::calculatePageTabWidth( GuiTabPageCtrl *page )
|
|
{
|
|
if( !page )
|
|
return mMinTabWidth;
|
|
|
|
const char* text = page->getText();
|
|
|
|
if( !text || dStrlen(text) == 0 || mProfile == NULL || mProfile->mFont == NULL )
|
|
return mMinTabWidth;
|
|
|
|
GFont *font = mProfile->mFont;
|
|
|
|
return font->getStrNWidth( text, dStrlen(text) );
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
const RectI GuiTabBookCtrl::getClientRect()
|
|
{
|
|
|
|
if( !mProfile || mProfile->mBitmapArrayRects.size() < NumBitmaps )
|
|
return Parent::getClientRect();
|
|
|
|
return mPageRect;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void GuiTabBookCtrl::calculatePageTabs()
|
|
{
|
|
// Short Circuit.
|
|
//
|
|
// If the tab size is zero, don't render tabs,
|
|
// assuming it's a tab-less book
|
|
if( mPages.empty() || mTabHeight <= 0 )
|
|
{
|
|
mPageRect.point.x = 0;
|
|
mPageRect.point.y = 0;
|
|
mPageRect.extent.x = getWidth();
|
|
mPageRect.extent.y = getHeight();
|
|
return;
|
|
}
|
|
|
|
S32 currRow = 0;
|
|
S32 currColumn = 0;
|
|
S32 currX = mFrontTabPadding;
|
|
S32 maxWidth = 0;
|
|
|
|
for( S32 i = 0; i < mPages.size(); i++ )
|
|
{
|
|
// Fetch Tab Width
|
|
S32 tabWidth = calculatePageTabWidth( mPages[i].Page ) + ( mTabMargin * 2 );
|
|
tabWidth = getMax( tabWidth, mMinTabWidth );
|
|
TabHeaderInfo &info = mPages[i];
|
|
switch( mTabPosition )
|
|
{
|
|
case AlignTop:
|
|
case AlignBottom:
|
|
// If we're going to go outside our bounds
|
|
// with this tab move it down a row
|
|
if( currX + tabWidth > getWidth() )
|
|
{
|
|
// Calculate and Advance State.
|
|
maxWidth = getMax( tabWidth, maxWidth );
|
|
balanceRow( currRow, currX );
|
|
info.TabRow = ++currRow;
|
|
// Reset Necessaries
|
|
info.TabColumn = currColumn = maxWidth = currX = 0;
|
|
}
|
|
else
|
|
{
|
|
info.TabRow = currRow;
|
|
info.TabColumn = currColumn++;
|
|
}
|
|
|
|
// Calculate Tabs Bounding Rect
|
|
info.TabRect.point.x = currX;
|
|
info.TabRect.extent.x = tabWidth;
|
|
info.TabRect.extent.y = mTabHeight;
|
|
|
|
// Adjust Y Point based on alignment
|
|
if( mTabPosition == AlignTop )
|
|
info.TabRect.point.y = ( info.TabRow * mTabHeight );
|
|
else
|
|
info.TabRect.point.y = getHeight() - ( ( 1 + info.TabRow ) * mTabHeight );
|
|
|
|
currX += tabWidth;
|
|
break;
|
|
};
|
|
}
|
|
|
|
currRow++;
|
|
currColumn++;
|
|
|
|
Point2I localPoint = getExtent();
|
|
|
|
// Calculate
|
|
switch( mTabPosition )
|
|
{
|
|
case AlignTop:
|
|
|
|
localPoint.y -= getTop();
|
|
|
|
mTabRect.point.x = 0;
|
|
mTabRect.extent.x = localPoint.x;
|
|
mTabRect.point.y = 0;
|
|
mTabRect.extent.y = currRow * mTabHeight;
|
|
|
|
mPageRect.point.x = 0;
|
|
mPageRect.point.y = mTabRect.extent.y;
|
|
mPageRect.extent.x = mTabRect.extent.x;
|
|
mPageRect.extent.y = getHeight() - mTabRect.extent.y;
|
|
|
|
break;
|
|
case AlignBottom:
|
|
mTabRect.point.x = 0;
|
|
mTabRect.extent.x = localPoint.x;
|
|
mTabRect.extent.y = currRow * mTabHeight;
|
|
mTabRect.point.y = getHeight() - mTabRect.extent.y;
|
|
|
|
mPageRect.point.x = 0;
|
|
mPageRect.point.y = 0;
|
|
mPageRect.extent.x = mTabRect.extent.x;
|
|
mPageRect.extent.y = localPoint.y - mTabRect.extent.y;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void GuiTabBookCtrl::balanceRow( S32 row, S32 totalTabWidth )
|
|
{
|
|
// Short Circuit.
|
|
//
|
|
// If the tab size is zero, don't render tabs,
|
|
// and assume it's a tab-less tab-book - JDD
|
|
if( mPages.empty() || mTabHeight <= 0 )
|
|
return;
|
|
|
|
Vector<TabHeaderInfo*> rowTemp;
|
|
rowTemp.clear();
|
|
|
|
for( S32 i = 0; i < mPages.size(); i++ )
|
|
{
|
|
TabHeaderInfo &info = mPages[i];
|
|
|
|
if(info.TabRow == row )
|
|
rowTemp.push_back( &mPages[i] );
|
|
}
|
|
|
|
if( rowTemp.empty() )
|
|
return;
|
|
|
|
// Balance the tabs across the remaining space
|
|
S32 spaceToDivide = getWidth() - totalTabWidth;
|
|
S32 pointDelta = 0;
|
|
for( S32 i = 0; i < rowTemp.size(); i++ )
|
|
{
|
|
TabHeaderInfo &info = *rowTemp[i];
|
|
S32 extraSpace = (S32)spaceToDivide / ( rowTemp.size() );
|
|
info.TabRect.extent.x += extraSpace;
|
|
info.TabRect.point.x += pointDelta;
|
|
pointDelta += extraSpace;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
GuiTabPageCtrl *GuiTabBookCtrl::findHitTab( const GuiEvent &event )
|
|
{
|
|
return findHitTab( event.mousePoint );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
GuiTabPageCtrl *GuiTabBookCtrl::findHitTab( Point2I hitPoint )
|
|
{
|
|
// Short Circuit.
|
|
//
|
|
// If the tab size is zero, don't render tabs,
|
|
// and assume it's a tab-less tab-book - JDD
|
|
if( mPages.empty() || mTabHeight <= 0 )
|
|
return NULL;
|
|
|
|
for( S32 i = 0; i < mPages.size(); i++ )
|
|
{
|
|
if( mPages[i].TabRect.pointInRect( hitPoint ) )
|
|
return mPages[i].Page;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void GuiTabBookCtrl::selectPage( S32 index )
|
|
{
|
|
if( mPages.empty() || index < 0 )
|
|
return;
|
|
|
|
if( mPages.size() <= index )
|
|
index = mPages.size() - 1;
|
|
|
|
// Select the page
|
|
selectPage( mPages[ index ].Page );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void GuiTabBookCtrl::selectPage( GuiTabPageCtrl *page )
|
|
{
|
|
// Return if already selected.
|
|
if( mSelectedPageNum >= 0 && mSelectedPageNum < mPages.size() && mPages[ mSelectedPageNum ].Page == page )
|
|
return;
|
|
|
|
mSelectedPageNum = -1;
|
|
|
|
Vector<TabHeaderInfo>::iterator i = mPages.begin();
|
|
for( S32 index = 0; i != mPages.end() ; i++, index++ )
|
|
{
|
|
GuiTabPageCtrl *tab = (*i).Page;
|
|
if( page == tab )
|
|
{
|
|
mActivePage = tab;
|
|
tab->setVisible( true );
|
|
|
|
mSelectedPageNum = index;
|
|
|
|
// Notify User
|
|
onTabSelected_callback( tab->getText(), index );
|
|
}
|
|
else
|
|
{
|
|
tab->setVisible(false);
|
|
onTabUnSelected_callback(tab->getText(), index);
|
|
}
|
|
}
|
|
setUpdateLayout( updateSelf );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool GuiTabBookCtrl::_setSelectedPage( void *object, const char *index, const char *data )
|
|
{
|
|
GuiTabBookCtrl* book = reinterpret_cast< GuiTabBookCtrl* >( object );
|
|
book->selectPage( dAtoi( data ) );
|
|
return false;
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool GuiTabBookCtrl::onKeyDown(const GuiEvent &event)
|
|
{
|
|
// Tab = Next Page
|
|
// Ctrl-Tab = Previous Page
|
|
if( 0 && event.keyCode == KEY_TAB )
|
|
{
|
|
if( event.modifier & SI_PRIMARY_CTRL )
|
|
selectPrevPage();
|
|
else
|
|
selectNextPage();
|
|
|
|
return true;
|
|
}
|
|
|
|
return Parent::onKeyDown( event );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void GuiTabBookCtrl::selectNextPage()
|
|
{
|
|
if( mPages.empty() )
|
|
return;
|
|
|
|
if( mActivePage == NULL )
|
|
mActivePage = mPages[0].Page;
|
|
|
|
S32 nI = 0;
|
|
for( ; nI < mPages.size(); nI++ )
|
|
{
|
|
GuiTabPageCtrl *tab = mPages[ nI ].Page;
|
|
if( tab == mActivePage )
|
|
{
|
|
if( nI == ( mPages.size() - 1 ) )
|
|
selectPage( 0 );
|
|
else if ( nI + 1 <= ( mPages.size() - 1 ) )
|
|
selectPage( nI + 1 );
|
|
else
|
|
selectPage( 0 );
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void GuiTabBookCtrl::selectPrevPage()
|
|
{
|
|
if( mPages.empty() )
|
|
return;
|
|
|
|
if( mActivePage == NULL )
|
|
mActivePage = mPages[0].Page;
|
|
|
|
S32 nI = 0;
|
|
for( ; nI < mPages.size(); nI++ )
|
|
{
|
|
GuiTabPageCtrl *tab = mPages[ nI ].Page;
|
|
if( tab == mActivePage )
|
|
{
|
|
if( nI == 0 )
|
|
selectPage( mPages.size() - 1 );
|
|
else
|
|
selectPage( nI - 1 );
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void GuiTabBookCtrl::fitPage( GuiTabPageCtrl* page )
|
|
{
|
|
page->resize( mPageRect.point, mPageRect.extent );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
S32 GuiTabBookCtrl::getPageNum( GuiTabPageCtrl* page ) const
|
|
{
|
|
const U32 numPages = mPages.size();
|
|
for( U32 i = 0; i < numPages; ++ i )
|
|
if( mPages[ i ].Page == page )
|
|
return i;
|
|
|
|
return -1;
|
|
}
|
|
|
|
//=============================================================================
|
|
// API.
|
|
//=============================================================================
|
|
// MARK: ---- API ----
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( GuiTabBookCtrl, addPage, void, ( const char* title ), ( "" ),
|
|
"Add a new tab page to the control.\n\n"
|
|
"@param title Title text for the tab page header." )
|
|
{
|
|
object->addNewPage( title );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( GuiTabBookCtrl, selectPage, void, ( S32 index ),,
|
|
"Set the selected tab page.\n\n"
|
|
"@param index Index of the tab page." )
|
|
{
|
|
object->selectPage( index );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( GuiTabBookCtrl, getSelectedPage, S32, (),,
|
|
"Get the index of the currently selected tab page.\n\n"
|
|
"@return Index of the selected tab page or -1 if no tab page is selected." )
|
|
{
|
|
return object->getSelectedPageNum();
|
|
}
|