guiTabBookCtrl.cpp 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014
  1. //-----------------------------------------------------------------------------
  2. // Copyright (c) 2012 GarageGames, LLC
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to
  6. // deal in the Software without restriction, including without limitation the
  7. // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  8. // sell copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  20. // IN THE SOFTWARE.
  21. //-----------------------------------------------------------------------------
  22. #include "gui/containers/guiTabBookCtrl.h"
  23. #include "console/engineAPI.h"
  24. #include "gui/core/guiCanvas.h"
  25. #include "gui/editor/guiEditCtrl.h"
  26. #include "gui/controls/guiPopUpCtrl.h"
  27. #include "gui/core/guiDefaultControlRender.h"
  28. #include "gfx/gfxDrawUtil.h"
  29. #include "console/typeValidators.h"
  30. IMPLEMENT_CONOBJECT( GuiTabBookCtrl );
  31. ConsoleDocClass( GuiTabBookCtrl,
  32. "@brief A container \n\n"
  33. "@tsexample\n"
  34. "// Create \n"
  35. "@endtsexample\n\n"
  36. "@note Only GuiTabPageCtrls must be added to GuiTabBookCtrls. If an object of a different "
  37. "class is added to the control, it will be reassigned to either the active page or the "
  38. "tab book's parent.\n\n"
  39. "@see GuiTabPageCtrl\n"
  40. "@ingroup GuiContainers"
  41. );
  42. ImplementEnumType( GuiTabPosition,
  43. "Where the control should put the tab headers for selecting individual pages.\n\n"
  44. "@ingroup GuiContainers" )
  45. { GuiTabBookCtrl::AlignTop, "Top", "Tab headers on top edge." },
  46. { GuiTabBookCtrl::AlignBottom,"Bottom", "Tab headers on bottom edge." }
  47. EndImplementEnumType;
  48. IMPLEMENT_CALLBACK( GuiTabBookCtrl, onTabSelected, void, ( const String& text, U32 index ), ( text, index ),
  49. "Called when a new tab page is selected.\n\n"
  50. "@param text Text of the page header for the tab that is being selected.\n"
  51. "@param index Index of the tab page being selected." );
  52. IMPLEMENT_CALLBACK(GuiTabBookCtrl, onTabUnSelected, void, (const String& text, U32 index), (text, index),
  53. "Called when a new tab page is unselected.\n\n"
  54. "@param text Text of the page header for the tab that is being unselected.\n"
  55. "@param index Index of the tab page being unselected.");
  56. IMPLEMENT_CALLBACK( GuiTabBookCtrl, onTabRightClick, void, ( const String& text, U32 index ), ( text, index ),
  57. "Called when the user right-clicks on a tab page header.\n\n"
  58. "@param text Text of the page header for the tab that is being selected.\n"
  59. "@param index Index of the tab page being selected." );
  60. //-----------------------------------------------------------------------------
  61. GuiTabBookCtrl::GuiTabBookCtrl()
  62. {
  63. VECTOR_SET_ASSOCIATION( mPages );
  64. mTabHeight = 24;
  65. mTabPosition = AlignTop;
  66. mActivePage = NULL;
  67. mHoverTab = NULL;
  68. mHasTexture = false;
  69. mBitmapBounds = NULL;
  70. setExtent( 400, 300 );
  71. mPageRect = RectI(0,0,0,0);
  72. mTabRect = RectI(0,0,0,0);
  73. mFrontTabPadding = 0;
  74. mPages.reserve(12);
  75. mTabMargin = 7;
  76. mMinTabWidth = 64;
  77. mIsContainer = true;
  78. mSelectedPageNum = -1;
  79. mDefaultPageNum = -1;
  80. mAllowReorder = false;
  81. mDraggingTab = false;
  82. mDraggingTabRect = false;
  83. mIsFirstWake = true;
  84. }
  85. //-----------------------------------------------------------------------------
  86. void GuiTabBookCtrl::initPersistFields()
  87. {
  88. docsURL;
  89. addGroup( "TabBook" );
  90. addField( "tabPosition", TYPEID< TabPosition >(), Offset( mTabPosition, GuiTabBookCtrl ),
  91. "Where to place the tab page headers." );
  92. addFieldV( "tabMargin", TypeRangedS32, Offset( mTabMargin, GuiTabBookCtrl ), &CommonValidators::PositiveInt,
  93. "Spacing to put between individual tab page headers." );
  94. addFieldV( "minTabWidth", TypeRangedS32, Offset( mMinTabWidth, GuiTabBookCtrl ), &CommonValidators::PositiveInt,
  95. "Minimum width allocated to a tab page header." );
  96. addFieldV( "tabHeight", TypeRangedS32, Offset( mTabHeight, GuiTabBookCtrl ), &CommonValidators::PositiveInt,
  97. "Height of tab page headers." );
  98. addField( "allowReorder", TypeBool, Offset( mAllowReorder, GuiTabBookCtrl ),
  99. "Whether reordering tabs with the mouse is allowed." );
  100. addFieldV( "defaultPage", TypeRangedS32, Offset( mDefaultPageNum, GuiTabBookCtrl ), &CommonValidators::NegDefaultInt,
  101. "Index of page to select on first onWake() call (-1 to disable)." );
  102. addProtectedFieldV( "selectedPage", TypeRangedS32, Offset( mSelectedPageNum, GuiTabBookCtrl ),
  103. &_setSelectedPage, &defaultProtectedGetFn, &CommonValidators::PositiveInt,
  104. "Index of currently selected page." );
  105. addField( "frontTabPadding", TypeS32, Offset( mFrontTabPadding, GuiTabBookCtrl ),
  106. "X offset of first tab page header." );
  107. endGroup( "TabBook" );
  108. Parent::initPersistFields();
  109. }
  110. //-----------------------------------------------------------------------------
  111. void GuiTabBookCtrl::onChildRemoved( GuiControl* child )
  112. {
  113. for (S32 i = 0; i < mPages.size(); i++ )
  114. {
  115. GuiTabPageCtrl* tab = mPages[i].Page;
  116. if( tab == child )
  117. {
  118. mPages.erase( i );
  119. break;
  120. }
  121. }
  122. // Calculate Page Information
  123. calculatePageTabs();
  124. // Active Index.
  125. mSelectedPageNum = getMin( mSelectedPageNum, mPages.size() - 1 );
  126. if ( mSelectedPageNum != -1 )
  127. {
  128. // Select Page.
  129. selectPage( mSelectedPageNum );
  130. }
  131. }
  132. //-----------------------------------------------------------------------------
  133. void GuiTabBookCtrl::onChildAdded( GuiControl *child )
  134. {
  135. GuiTabPageCtrl *page = dynamic_cast<GuiTabPageCtrl*>(child);
  136. if( !page )
  137. {
  138. Con::warnf("GuiTabBookCtrl::onChildAdded - attempting to add NON GuiTabPageCtrl as child page");
  139. SimObject *simObj = reinterpret_cast<SimObject*>(child);
  140. removeObject( simObj );
  141. if( mActivePage )
  142. {
  143. mActivePage->addObject( simObj );
  144. }
  145. else
  146. {
  147. Con::warnf("GuiTabBookCtrl::onChildAdded - unable to find active page to reassign ownership of new child control to, placing on parent");
  148. GuiControl *rent = getParent();
  149. if( rent )
  150. rent->addObject( simObj );
  151. }
  152. return;
  153. }
  154. TabHeaderInfo newPage;
  155. newPage.Page = page;
  156. newPage.TabRow = -1;
  157. newPage.TabColumn = -1;
  158. mPages.push_back( newPage );
  159. // Calculate Page Information
  160. calculatePageTabs();
  161. if( page->getFitBook() )
  162. fitPage( page );
  163. // Select this Page
  164. selectPage( page );
  165. }
  166. //-----------------------------------------------------------------------------
  167. bool GuiTabBookCtrl::reOrder(SimObject* obj, SimObject* target)
  168. {
  169. if ( !Parent::reOrder(obj, target) )
  170. return false;
  171. // Store the Selected Page.
  172. GuiTabPageCtrl *selectedPage = NULL;
  173. if ( mSelectedPageNum != -1 )
  174. selectedPage = mPages[mSelectedPageNum].Page;
  175. // Determine the Target Page Index.
  176. S32 targetIndex = -1;
  177. for( S32 i = 0; i < mPages.size(); i++ )
  178. {
  179. const TabHeaderInfo &info = mPages[i];
  180. if ( info.Page == target )
  181. {
  182. targetIndex = i;
  183. break;
  184. }
  185. }
  186. if ( targetIndex == -1 )
  187. {
  188. return false;
  189. }
  190. for( S32 i = 0; i < mPages.size(); i++ )
  191. {
  192. const TabHeaderInfo &info = mPages[i];
  193. if ( info.Page == obj )
  194. {
  195. // Store Info.
  196. TabHeaderInfo objPage = info;
  197. // Remove.
  198. mPages.erase( i );
  199. // Insert.
  200. mPages.insert( targetIndex, objPage );
  201. break;
  202. }
  203. }
  204. // Update Tabs.
  205. calculatePageTabs();
  206. // Reselect Page.
  207. selectPage( selectedPage );
  208. return true;
  209. }
  210. //-----------------------------------------------------------------------------
  211. bool GuiTabBookCtrl::acceptsAsChild( SimObject* object ) const
  212. {
  213. // Only accept tab pages.
  214. return ( dynamic_cast< GuiTabPageCtrl* >( object ) != NULL );
  215. }
  216. //-----------------------------------------------------------------------------
  217. bool GuiTabBookCtrl::onWake()
  218. {
  219. if (! Parent::onWake())
  220. return false;
  221. mHasTexture = mProfile->constructBitmapArray() > 0;
  222. if( mHasTexture )
  223. {
  224. mBitmapBounds = mProfile->mBitmapArrayRects.address();
  225. mTabHeight = mBitmapBounds[TabSelected].extent.y;
  226. }
  227. calculatePageTabs();
  228. if( mIsFirstWake )
  229. {
  230. // Awaken all pages, visible or not. We need to do this so
  231. // any pages that make use of a language table for their label
  232. // are correctly initialized.
  233. for ( U32 i = 0; i < mPages.size(); ++i)
  234. {
  235. if ( !mPages[i].Page->isAwake() )
  236. {
  237. mPages[i].Page->awaken();
  238. }
  239. }
  240. if( mDefaultPageNum >= 0 && mDefaultPageNum < mPages.size() )
  241. selectPage( mDefaultPageNum );
  242. mIsFirstWake = false;
  243. }
  244. return true;
  245. }
  246. //-----------------------------------------------------------------------------
  247. void GuiTabBookCtrl::addNewPage( const char* text )
  248. {
  249. GuiTabPageCtrl* page = new GuiTabPageCtrl();
  250. if( text )
  251. page->setText( text );
  252. page->registerObject();
  253. addObject( page );
  254. }
  255. //-----------------------------------------------------------------------------
  256. bool GuiTabBookCtrl::resize(const Point2I &newPosition, const Point2I &newExtent)
  257. {
  258. bool result = Parent::resize( newPosition, newExtent );
  259. calculatePageTabs();
  260. for (S32 i = 0; i < mPages.size(); i++)
  261. {
  262. const TabHeaderInfo& info = mPages[i];
  263. GuiTabPageCtrl* page = info.Page;
  264. if(page->getFitBook())
  265. fitPage(page);
  266. }
  267. return result;
  268. }
  269. //-----------------------------------------------------------------------------
  270. void GuiTabBookCtrl::childResized(GuiControl *child)
  271. {
  272. Parent::childResized( child );
  273. //child->resize( mPageRect.point, mPageRect.extent );
  274. }
  275. //-----------------------------------------------------------------------------
  276. void GuiTabBookCtrl::onMouseDown(const GuiEvent &event)
  277. {
  278. mDraggingTab = false;
  279. mDraggingTabRect = false;
  280. Point2I localMouse = globalToLocalCoord( event.mousePoint );
  281. if( mTabRect.pointInRect( localMouse ) )
  282. {
  283. GuiTabPageCtrl *tab = findHitTab( localMouse );
  284. if( tab != NULL )
  285. {
  286. selectPage( tab );
  287. mDraggingTab = mAllowReorder;
  288. }
  289. else
  290. {
  291. mDraggingTabRect = true;
  292. }
  293. }
  294. }
  295. //-----------------------------------------------------------------------------
  296. void GuiTabBookCtrl::onMouseUp(const GuiEvent &event)
  297. {
  298. Parent::onMouseUp( event );
  299. mDraggingTab = false;
  300. mDraggingTabRect = false;
  301. }
  302. //-----------------------------------------------------------------------------
  303. void GuiTabBookCtrl::onMouseDragged(const GuiEvent &event)
  304. {
  305. Parent::onMouseDragged( event );
  306. if ( !mDraggingTab )
  307. return;
  308. GuiTabPageCtrl *selectedPage = NULL;
  309. if ( mSelectedPageNum != -1 )
  310. selectedPage = mPages[mSelectedPageNum].Page;
  311. if ( !selectedPage )
  312. return;
  313. Point2I localMouse = globalToLocalCoord( event.mousePoint );
  314. if( mTabRect.pointInRect( localMouse ) )
  315. {
  316. GuiTabPageCtrl *tab = findHitTab( localMouse );
  317. if( tab != NULL && tab != selectedPage )
  318. {
  319. S32 targetIndex = -1;
  320. for( S32 i = 0; i < mPages.size(); i++ )
  321. {
  322. if( mPages[i].Page == tab )
  323. {
  324. targetIndex = i;
  325. break;
  326. }
  327. }
  328. if ( targetIndex > mSelectedPageNum )
  329. {
  330. reOrder( tab, selectedPage );
  331. }
  332. else
  333. {
  334. reOrder( selectedPage, tab );
  335. }
  336. }
  337. }
  338. }
  339. //-----------------------------------------------------------------------------
  340. void GuiTabBookCtrl::onMouseMove(const GuiEvent &event)
  341. {
  342. Point2I localMouse = globalToLocalCoord( event.mousePoint );
  343. if( mTabRect.pointInRect( localMouse ) )
  344. {
  345. GuiTabPageCtrl *tab = findHitTab( localMouse );
  346. if( tab != NULL && mHoverTab != tab )
  347. mHoverTab = tab;
  348. else if ( !tab )
  349. mHoverTab = NULL;
  350. }
  351. Parent::onMouseMove( event );
  352. }
  353. //-----------------------------------------------------------------------------
  354. void GuiTabBookCtrl::onMouseLeave( const GuiEvent &event )
  355. {
  356. Parent::onMouseLeave( event );
  357. if(mDraggingTab)
  358. {
  359. //we dragged the tab out, so do something about that
  360. GuiTabPageCtrl* selectedPage = NULL;
  361. if (mSelectedPageNum != -1)
  362. selectedPage = mPages[mSelectedPageNum].Page;
  363. mDraggingTab = false;
  364. Con::executef(this, "onTabDraggedOut", selectedPage->getIdString());
  365. }
  366. mHoverTab = NULL;
  367. }
  368. //-----------------------------------------------------------------------------
  369. bool GuiTabBookCtrl::onMouseDownEditor(const GuiEvent &event, Point2I offset)
  370. {
  371. bool handled = false;
  372. Point2I localMouse = globalToLocalCoord( event.mousePoint );
  373. if( mTabRect.pointInRect( localMouse ) )
  374. {
  375. GuiTabPageCtrl *tab = findHitTab( localMouse );
  376. if( tab != NULL )
  377. {
  378. selectPage( tab );
  379. handled = true;
  380. }
  381. }
  382. #ifdef TORQUE_TOOLS
  383. // This shouldn't be called if it's not design time, but check just incase
  384. if ( GuiControl::smDesignTime )
  385. {
  386. // If we clicked in the editor and our addset is the tab book
  387. // ctrl, select the child ctrl so we can edit it's properties
  388. GuiEditCtrl* edit = GuiControl::smEditorHandle;
  389. if( edit && ( edit->getAddSet() == this ) && mActivePage != NULL )
  390. edit->select( mActivePage );
  391. }
  392. #endif
  393. // Return whether we handled this or not.
  394. return handled;
  395. }
  396. //-----------------------------------------------------------------------------
  397. void GuiTabBookCtrl::onRightMouseUp( const GuiEvent& event )
  398. {
  399. Point2I localMouse = globalToLocalCoord( event.mousePoint );
  400. if( mTabRect.pointInRect( localMouse ) )
  401. {
  402. GuiTabPageCtrl* tab = findHitTab( localMouse );
  403. if( tab )
  404. onTabRightClick_callback( tab->getText(), getPageNum( tab ) );
  405. }
  406. }
  407. //-----------------------------------------------------------------------------
  408. void GuiTabBookCtrl::onRender(Point2I offset, const RectI &updateRect)
  409. {
  410. RectI tabRect = mTabRect;
  411. tabRect.point += offset;
  412. RectI pageRect = mPageRect;
  413. pageRect.point += offset;
  414. // We're so nice we'll store the old modulation before we clear it for our rendering! :)
  415. ColorI oldModulation;
  416. GFX->getDrawUtil()->getBitmapModulation( &oldModulation );
  417. // Wipe it out
  418. GFX->getDrawUtil()->clearBitmapModulation();
  419. Parent::onRender(offset, updateRect);
  420. // Clip to tab area
  421. RectI savedClipRect = GFX->getClipRect();
  422. RectI clippedTabRect = tabRect;
  423. clippedTabRect.intersect( savedClipRect );
  424. GFX->setClipRect( clippedTabRect );
  425. // Render our tabs
  426. renderTabs( offset, tabRect );
  427. // Restore Rect.
  428. GFX->setClipRect( savedClipRect );
  429. // Restore old modulation
  430. GFX->getDrawUtil()->setBitmapModulation( oldModulation );
  431. }
  432. //-----------------------------------------------------------------------------
  433. void GuiTabBookCtrl::renderTabs( const Point2I &offset, const RectI &tabRect )
  434. {
  435. // If the tab size is zero, don't render tabs,
  436. // assuming it's a tab-less book
  437. if( mPages.empty() || mTabHeight <= 0 )
  438. return;
  439. for( S32 i = 0; i < mPages.size(); i++ )
  440. {
  441. const TabHeaderInfo &currentTabInfo = mPages[i];
  442. RectI tabBounds = mPages[i].TabRect;
  443. tabBounds.point += offset;
  444. GuiTabPageCtrl *tab = mPages[i].Page;
  445. if( tab != NULL )
  446. renderTab( tabBounds, tab );
  447. // If we're on the last tab, draw the nice end piece
  448. if( i + 1 == mPages.size() )
  449. {
  450. Point2I tabEndPoint = Point2I(currentTabInfo.TabRect.point.x + currentTabInfo.TabRect.extent.x + offset.x, currentTabInfo.TabRect.point.y + offset.y);
  451. Point2I tabEndExtent = Point2I((tabRect.point.x + tabRect.extent.x) - tabEndPoint.x, currentTabInfo.TabRect.extent.y);
  452. RectI tabEndRect = RectI(tabEndPoint,tabEndExtent);
  453. GFX->setClipRect( tabEndRect );
  454. // As it turns out the last tab can be outside the viewport in which
  455. // case trying to render causes a DX assert. Could be better if
  456. // setClipRect returned a bool.
  457. if ( GFX->getViewport().isValidRect() )
  458. renderFixedBitmapBordersFilled( tabEndRect, TabEnds + 1, mProfile );
  459. }
  460. }
  461. }
  462. //-----------------------------------------------------------------------------
  463. void GuiTabBookCtrl::renderTab(const RectI& tabRect, GuiTabPageCtrl *tab)
  464. {
  465. StringTableEntry text = tab->getText();
  466. ColorI oldColor;
  467. GFX->getDrawUtil()->getBitmapModulation( &oldColor );
  468. // Is this a skinned control?
  469. if( mHasTexture && mProfile->mBitmapArrayRects.size() >= 9 )
  470. {
  471. S32 indexMultiplier = 1;
  472. switch( mTabPosition )
  473. {
  474. case AlignTop:
  475. case AlignBottom:
  476. if ( mActivePage == tab )
  477. indexMultiplier += TabSelected;
  478. else if( mHoverTab == tab )
  479. indexMultiplier += TabHover;
  480. else
  481. indexMultiplier += TabNormal;
  482. break;
  483. }
  484. renderFixedBitmapBordersFilled( tabRect, indexMultiplier, mProfile );
  485. }
  486. else
  487. {
  488. // If this isn't a skinned control or the bitmap is simply missing, handle it WELL
  489. if ( mActivePage == tab )
  490. GFX->getDrawUtil()->drawRectFill(tabRect, mProfile->mFillColor);
  491. else if( mHoverTab == tab )
  492. GFX->getDrawUtil()->drawRectFill(tabRect, mProfile->mFillColorHL);
  493. else
  494. GFX->getDrawUtil()->drawRectFill(tabRect, mProfile->mFillColorNA);
  495. }
  496. GFX->getDrawUtil()->setBitmapModulation(mProfile->mFontColor);
  497. switch( mTabPosition )
  498. {
  499. case AlignTop:
  500. case AlignBottom:
  501. renderJustifiedText( tabRect.point, tabRect.extent, text);
  502. break;
  503. }
  504. GFX->getDrawUtil()->setBitmapModulation( oldColor);
  505. }
  506. //-----------------------------------------------------------------------------
  507. void GuiTabBookCtrl::setUpdate()
  508. {
  509. Parent::setUpdate();
  510. setUpdateRegion(Point2I(0,0), getExtent());
  511. calculatePageTabs();
  512. }
  513. //-----------------------------------------------------------------------------
  514. S32 GuiTabBookCtrl::calculatePageTabWidth( GuiTabPageCtrl *page )
  515. {
  516. if( !page )
  517. return mMinTabWidth;
  518. const char* text = page->getText();
  519. if( !text || dStrlen(text) == 0 || mProfile == NULL || mProfile->mFont == NULL )
  520. return mMinTabWidth;
  521. GFont *font = mProfile->mFont;
  522. return font->getStrNWidth( text, dStrlen(text) );
  523. }
  524. //-----------------------------------------------------------------------------
  525. const RectI GuiTabBookCtrl::getClientRect()
  526. {
  527. if( !mProfile || mProfile->mBitmapArrayRects.size() < NumBitmaps )
  528. return Parent::getClientRect();
  529. return mPageRect;
  530. }
  531. //-----------------------------------------------------------------------------
  532. void GuiTabBookCtrl::calculatePageTabs()
  533. {
  534. // Short Circuit.
  535. //
  536. // If the tab size is zero, don't render tabs,
  537. // assuming it's a tab-less book
  538. if( mPages.empty() || mTabHeight <= 0 )
  539. {
  540. mPageRect.point.x = 0;
  541. mPageRect.point.y = 0;
  542. mPageRect.extent.x = getWidth();
  543. mPageRect.extent.y = getHeight();
  544. return;
  545. }
  546. S32 currRow = 0;
  547. S32 currColumn = 0;
  548. S32 currX = mFrontTabPadding;
  549. S32 maxWidth = 0;
  550. for( S32 i = 0; i < mPages.size(); i++ )
  551. {
  552. // Fetch Tab Width
  553. S32 tabWidth = calculatePageTabWidth( mPages[i].Page ) + ( mTabMargin * 2 );
  554. tabWidth = getMax( tabWidth, mMinTabWidth );
  555. TabHeaderInfo &info = mPages[i];
  556. switch( mTabPosition )
  557. {
  558. case AlignTop:
  559. case AlignBottom:
  560. // If we're going to go outside our bounds
  561. // with this tab move it down a row
  562. if( currX + tabWidth > getWidth() )
  563. {
  564. // Calculate and Advance State.
  565. maxWidth = getMax( tabWidth, maxWidth );
  566. balanceRow( currRow, currX );
  567. info.TabRow = ++currRow;
  568. // Reset Necessaries
  569. info.TabColumn = currColumn = maxWidth = currX = 0;
  570. }
  571. else
  572. {
  573. info.TabRow = currRow;
  574. info.TabColumn = currColumn++;
  575. }
  576. // Calculate Tabs Bounding Rect
  577. info.TabRect.point.x = currX;
  578. info.TabRect.extent.x = tabWidth;
  579. info.TabRect.extent.y = mTabHeight;
  580. // Adjust Y Point based on alignment
  581. if( mTabPosition == AlignTop )
  582. info.TabRect.point.y = ( info.TabRow * mTabHeight );
  583. else
  584. info.TabRect.point.y = getHeight() - ( ( 1 + info.TabRow ) * mTabHeight );
  585. currX += tabWidth;
  586. break;
  587. };
  588. }
  589. currRow++;
  590. currColumn++;
  591. Point2I localPoint = getExtent();
  592. // Calculate
  593. switch( mTabPosition )
  594. {
  595. case AlignTop:
  596. localPoint.y -= getTop();
  597. mTabRect.point.x = 0;
  598. mTabRect.extent.x = localPoint.x;
  599. mTabRect.point.y = 0;
  600. mTabRect.extent.y = currRow * mTabHeight;
  601. mPageRect.point.x = 0;
  602. mPageRect.point.y = mTabRect.extent.y;
  603. mPageRect.extent.x = mTabRect.extent.x;
  604. mPageRect.extent.y = getHeight() - mTabRect.extent.y;
  605. break;
  606. case AlignBottom:
  607. mTabRect.point.x = 0;
  608. mTabRect.extent.x = localPoint.x;
  609. mTabRect.extent.y = currRow * mTabHeight;
  610. mTabRect.point.y = getHeight() - mTabRect.extent.y;
  611. mPageRect.point.x = 0;
  612. mPageRect.point.y = 0;
  613. mPageRect.extent.x = mTabRect.extent.x;
  614. mPageRect.extent.y = localPoint.y - mTabRect.extent.y;
  615. break;
  616. }
  617. }
  618. //-----------------------------------------------------------------------------
  619. void GuiTabBookCtrl::balanceRow( S32 row, S32 totalTabWidth )
  620. {
  621. // Short Circuit.
  622. //
  623. // If the tab size is zero, don't render tabs,
  624. // and assume it's a tab-less tab-book - JDD
  625. if( mPages.empty() || mTabHeight <= 0 )
  626. return;
  627. Vector<TabHeaderInfo*> rowTemp;
  628. rowTemp.clear();
  629. for( S32 i = 0; i < mPages.size(); i++ )
  630. {
  631. TabHeaderInfo &info = mPages[i];
  632. if(info.TabRow == row )
  633. rowTemp.push_back( &mPages[i] );
  634. }
  635. if( rowTemp.empty() )
  636. return;
  637. // Balance the tabs across the remaining space
  638. S32 spaceToDivide = getWidth() - totalTabWidth;
  639. S32 pointDelta = 0;
  640. for( S32 i = 0; i < rowTemp.size(); i++ )
  641. {
  642. TabHeaderInfo &info = *rowTemp[i];
  643. S32 extraSpace = (S32)spaceToDivide / ( rowTemp.size() );
  644. info.TabRect.extent.x += extraSpace;
  645. info.TabRect.point.x += pointDelta;
  646. pointDelta += extraSpace;
  647. }
  648. }
  649. //-----------------------------------------------------------------------------
  650. GuiTabPageCtrl *GuiTabBookCtrl::findHitTab( const GuiEvent &event )
  651. {
  652. return findHitTab( event.mousePoint );
  653. }
  654. //-----------------------------------------------------------------------------
  655. GuiTabPageCtrl *GuiTabBookCtrl::findHitTab( Point2I hitPoint )
  656. {
  657. // Short Circuit.
  658. //
  659. // If the tab size is zero, don't render tabs,
  660. // and assume it's a tab-less tab-book - JDD
  661. if( mPages.empty() || mTabHeight <= 0 )
  662. return NULL;
  663. for( S32 i = 0; i < mPages.size(); i++ )
  664. {
  665. if( mPages[i].TabRect.pointInRect( hitPoint ) )
  666. return mPages[i].Page;
  667. }
  668. return NULL;
  669. }
  670. //-----------------------------------------------------------------------------
  671. void GuiTabBookCtrl::selectPage( S32 index )
  672. {
  673. if( mPages.empty() || index < 0 )
  674. return;
  675. if( mPages.size() <= index )
  676. index = mPages.size() - 1;
  677. // Select the page
  678. selectPage( mPages[ index ].Page );
  679. }
  680. //-----------------------------------------------------------------------------
  681. void GuiTabBookCtrl::selectPage( GuiTabPageCtrl *page )
  682. {
  683. // Return if already selected.
  684. if( mSelectedPageNum >= 0 && mSelectedPageNum < mPages.size() && mPages[ mSelectedPageNum ].Page == page )
  685. return;
  686. mSelectedPageNum = -1;
  687. Vector<TabHeaderInfo>::iterator i = mPages.begin();
  688. for( S32 index = 0; i != mPages.end() ; i++, index++ )
  689. {
  690. GuiTabPageCtrl *tab = (*i).Page;
  691. if( page == tab )
  692. {
  693. mActivePage = tab;
  694. tab->setVisible( true );
  695. mSelectedPageNum = index;
  696. // Notify User
  697. onTabSelected_callback( tab->getText(), index );
  698. }
  699. else
  700. {
  701. tab->setVisible(false);
  702. onTabUnSelected_callback(tab->getText(), index);
  703. }
  704. }
  705. setUpdateLayout( updateSelf );
  706. }
  707. //-----------------------------------------------------------------------------
  708. bool GuiTabBookCtrl::_setSelectedPage( void *object, const char *index, const char *data )
  709. {
  710. GuiTabBookCtrl* book = reinterpret_cast< GuiTabBookCtrl* >( object );
  711. book->selectPage( dAtoi( data ) );
  712. return false;
  713. }
  714. //-----------------------------------------------------------------------------
  715. bool GuiTabBookCtrl::onKeyDown(const GuiEvent &event)
  716. {
  717. // Tab = Next Page
  718. // Ctrl-Tab = Previous Page
  719. if( 0 && event.keyCode == KEY_TAB )
  720. {
  721. if( event.modifier & SI_PRIMARY_CTRL )
  722. selectPrevPage();
  723. else
  724. selectNextPage();
  725. return true;
  726. }
  727. return Parent::onKeyDown( event );
  728. }
  729. //-----------------------------------------------------------------------------
  730. void GuiTabBookCtrl::selectNextPage()
  731. {
  732. if( mPages.empty() )
  733. return;
  734. if( mActivePage == NULL )
  735. mActivePage = mPages[0].Page;
  736. S32 nI = 0;
  737. for( ; nI < mPages.size(); nI++ )
  738. {
  739. GuiTabPageCtrl *tab = mPages[ nI ].Page;
  740. if( tab == mActivePage )
  741. {
  742. if( nI == ( mPages.size() - 1 ) )
  743. selectPage( 0 );
  744. else if ( nI + 1 <= ( mPages.size() - 1 ) )
  745. selectPage( nI + 1 );
  746. else
  747. selectPage( 0 );
  748. return;
  749. }
  750. }
  751. }
  752. //-----------------------------------------------------------------------------
  753. void GuiTabBookCtrl::selectPrevPage()
  754. {
  755. if( mPages.empty() )
  756. return;
  757. if( mActivePage == NULL )
  758. mActivePage = mPages[0].Page;
  759. S32 nI = 0;
  760. for( ; nI < mPages.size(); nI++ )
  761. {
  762. GuiTabPageCtrl *tab = mPages[ nI ].Page;
  763. if( tab == mActivePage )
  764. {
  765. if( nI == 0 )
  766. selectPage( mPages.size() - 1 );
  767. else
  768. selectPage( nI - 1 );
  769. return;
  770. }
  771. }
  772. }
  773. //-----------------------------------------------------------------------------
  774. void GuiTabBookCtrl::fitPage( GuiTabPageCtrl* page )
  775. {
  776. page->resize( mPageRect.point, mPageRect.extent );
  777. }
  778. //-----------------------------------------------------------------------------
  779. S32 GuiTabBookCtrl::getPageNum( GuiTabPageCtrl* page ) const
  780. {
  781. const U32 numPages = mPages.size();
  782. for( U32 i = 0; i < numPages; ++ i )
  783. if( mPages[ i ].Page == page )
  784. return i;
  785. return -1;
  786. }
  787. //=============================================================================
  788. // API.
  789. //=============================================================================
  790. // MARK: ---- API ----
  791. //-----------------------------------------------------------------------------
  792. DefineEngineMethod( GuiTabBookCtrl, addPage, void, ( const char* title ), ( "" ),
  793. "Add a new tab page to the control.\n\n"
  794. "@param title Title text for the tab page header." )
  795. {
  796. object->addNewPage( title );
  797. }
  798. //-----------------------------------------------------------------------------
  799. DefineEngineMethod( GuiTabBookCtrl, selectPage, void, ( S32 index ),,
  800. "Set the selected tab page.\n\n"
  801. "@param index Index of the tab page." )
  802. {
  803. object->selectPage( index );
  804. }
  805. //-----------------------------------------------------------------------------
  806. DefineEngineMethod( GuiTabBookCtrl, getSelectedPage, S32, (),,
  807. "Get the index of the currently selected tab page.\n\n"
  808. "@return Index of the selected tab page or -1 if no tab page is selected." )
  809. {
  810. return object->getSelectedPageNum();
  811. }