guiArrayCtrl.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569
  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 "platform/platform.h"
  23. #include "gui/core/guiArrayCtrl.h"
  24. #include "console/console.h"
  25. #include "console/engineAPI.h"
  26. #include "gui/containers/guiScrollCtrl.h"
  27. #include "gfx/gfxDrawUtil.h"
  28. #include "gui/core/guiDefaultControlRender.h"
  29. IMPLEMENT_CONOBJECT(GuiArrayCtrl);
  30. ConsoleDocClass( GuiArrayCtrl,
  31. "@brief Abstract base class for controls that store and display multiple elements in a single view.\n\n"
  32. "You cannot actually instantiate this class. Instead you can use its childre:\n\n"
  33. "- GuiConsole\n"
  34. "- GuiTextListCtrl\n"
  35. "- GuiTreeViewCtrl\n"
  36. "- DbgFileView\n"
  37. "- CreatorTree\n"
  38. "This base class is primarily used by other internal classes or those dedicated to editors.\n\n"
  39. "@ingroup GuiCore\n"
  40. "@internal"
  41. );
  42. IMPLEMENT_CALLBACK( GuiArrayCtrl, onCellSelected, void, ( const Point2I& cell ), ( cell ),
  43. "Call when a cell in the array is selected (clicked).\n\n"
  44. "@param @cell Coordinates of the cell"
  45. );
  46. IMPLEMENT_CALLBACK( GuiArrayCtrl, onCellHighlighted, void, ( const Point2I& cell ), ( cell ),
  47. "Call when a cell in the array is highlighted (moused over).\n\n"
  48. "@param @cell Coordinates of the cell"
  49. );
  50. //-----------------------------------------------------------------------------
  51. GuiArrayCtrl::GuiArrayCtrl()
  52. {
  53. mActive = true;
  54. mCellSize.set(80, 30);
  55. mSize = Point2I(5, 30);
  56. mSelectedCell.set(-1, -1);
  57. mMouseOverCell.set(-1, -1);
  58. mHeaderDim.set(0, 0);
  59. mIsContainer = true;
  60. }
  61. //-----------------------------------------------------------------------------
  62. bool GuiArrayCtrl::onWake()
  63. {
  64. if (! Parent::onWake())
  65. return false;
  66. //get the font
  67. mFont = mProfile->mFont;
  68. return true;
  69. }
  70. //-----------------------------------------------------------------------------
  71. void GuiArrayCtrl::onSleep()
  72. {
  73. Parent::onSleep();
  74. mFont = NULL;
  75. }
  76. //-----------------------------------------------------------------------------
  77. void GuiArrayCtrl::setSize(Point2I newSize)
  78. {
  79. mSize = newSize;
  80. Point2I newExtent(newSize.x * mCellSize.x + mHeaderDim.x, newSize.y * mCellSize.y + mHeaderDim.y);
  81. setExtent(newExtent);
  82. }
  83. //-----------------------------------------------------------------------------
  84. void GuiArrayCtrl::getScrollDimensions(S32 &cell_size, S32 &num_cells)
  85. {
  86. cell_size = mCellSize.y;
  87. num_cells = mSize.y;
  88. }
  89. //-----------------------------------------------------------------------------
  90. bool GuiArrayCtrl::cellSelected(Point2I cell)
  91. {
  92. if (cell.x < 0 || cell.x >= mSize.x || cell.y < 0 || cell.y >= mSize.y)
  93. {
  94. mSelectedCell = Point2I(-1,-1);
  95. return false;
  96. }
  97. mSelectedCell = cell;
  98. scrollSelectionVisible();
  99. onCellSelected(cell);
  100. setUpdate();
  101. return true;
  102. }
  103. //-----------------------------------------------------------------------------
  104. void GuiArrayCtrl::onCellSelected(Point2I cell)
  105. {
  106. // [rene, 21-Jan-11 ] clashes with callbacks defined in derived classes
  107. Con::executef(this, "onSelect", Con::getFloatArg(cell.x), Con::getFloatArg(cell.y));
  108. onCellSelected_callback( cell );
  109. //call the console function
  110. execConsoleCallback();
  111. }
  112. //-----------------------------------------------------------------------------
  113. void GuiArrayCtrl::onCellHighlighted(Point2I cell)
  114. {
  115. onCellHighlighted_callback( cell );
  116. }
  117. //-----------------------------------------------------------------------------
  118. void GuiArrayCtrl::setSelectedCell(Point2I cell)
  119. {
  120. cellSelected(cell);
  121. }
  122. //-----------------------------------------------------------------------------
  123. Point2I GuiArrayCtrl::getSelectedCell()
  124. {
  125. return mSelectedCell;
  126. }
  127. //-----------------------------------------------------------------------------
  128. void GuiArrayCtrl::scrollSelectionVisible()
  129. {
  130. scrollCellVisible(mSelectedCell);
  131. }
  132. //-----------------------------------------------------------------------------
  133. void GuiArrayCtrl::scrollCellVisible(Point2I cell)
  134. {
  135. //make sure we have a parent
  136. //make sure we have a valid cell selected
  137. GuiScrollCtrl *parent = dynamic_cast<GuiScrollCtrl*>(getParent());
  138. if(!parent || cell.x < 0 || cell.y < 0)
  139. return;
  140. RectI cellBounds(cell.x * mCellSize.x, cell.y * mCellSize.y, mCellSize.x, mCellSize.y);
  141. parent->scrollRectVisible(cellBounds);
  142. }
  143. //-----------------------------------------------------------------------------
  144. void GuiArrayCtrl::onRenderColumnHeaders(Point2I offset, Point2I parentOffset, Point2I headerDim)
  145. {
  146. if (mProfile->mBorder)
  147. {
  148. RectI cellR(offset.x + headerDim.x, parentOffset.y, getWidth() - headerDim.x, headerDim.y);
  149. GFX->getDrawUtil()->drawRectFill(cellR, mProfile->mBorderColor);
  150. }
  151. }
  152. //-----------------------------------------------------------------------------
  153. void GuiArrayCtrl::onRenderRowHeader(Point2I offset, Point2I parentOffset, Point2I headerDim, Point2I cell)
  154. {
  155. ColorI color;
  156. RectI cellR;
  157. if (cell.x % 2)
  158. color.set(255, 0, 0, 255);
  159. else
  160. color.set(0, 255, 0, 255);
  161. cellR.point.set(parentOffset.x, offset.y);
  162. cellR.extent.set(headerDim.x, mCellSize.y);
  163. GFX->getDrawUtil()->drawRectFill(cellR, color);
  164. }
  165. //-----------------------------------------------------------------------------
  166. void GuiArrayCtrl::onRenderCell(Point2I offset, Point2I cell, bool selected, bool mouseOver)
  167. {
  168. ColorI color(255 * (cell.x % 2), 255 * (cell.y % 2), 255 * ((cell.x + cell.y) % 2), 255);
  169. if (selected)
  170. {
  171. color.set(255, 0, 0, 255);
  172. }
  173. else if (mouseOver)
  174. {
  175. color.set(0, 0, 255, 255);
  176. }
  177. //draw the cell
  178. RectI cellR(offset.x, offset.y, mCellSize.x, mCellSize.y);
  179. GFX->getDrawUtil()->drawRectFill(cellR, color);
  180. }
  181. //-----------------------------------------------------------------------------
  182. void GuiArrayCtrl::onRender(Point2I offset, const RectI &updateRect)
  183. {
  184. // The unmodified offset which was passed into this method.
  185. const Point2I inOffset( offset );
  186. //Parent::onRender( offset, updateRect );
  187. // We render our fill, borders, and child controls ourself.
  188. // This allows us to render child controls after we render cells,
  189. // so child controls appear on-top, as they should.
  190. // Render our fill and borders.
  191. // This code from GuiControl::onRender().
  192. {
  193. RectI ctrlRect(offset, getExtent());
  194. //if opaque, fill the update rect with the fill color
  195. if ( mProfile->mOpaque )
  196. GFX->getDrawUtil()->drawRectFill(ctrlRect, mProfile->mFillColor);
  197. //if there's a border, draw the border
  198. if ( mProfile->mBorder )
  199. renderBorder(ctrlRect, mProfile);
  200. }
  201. //make sure we have a parent
  202. GuiControl *parent = getParent();
  203. if (! parent)
  204. return;
  205. S32 i, j;
  206. RectI headerClip;
  207. RectI clipRect(updateRect.point, updateRect.extent);
  208. Point2I parentOffset = parent->localToGlobalCoord(Point2I(0, 0));
  209. //if we have column headings
  210. if (mHeaderDim.y > 0)
  211. {
  212. headerClip.point.x = parentOffset.x + mHeaderDim.x;
  213. headerClip.point.y = parentOffset.y;
  214. headerClip.extent.x = clipRect.extent.x;// - headerClip.point.x; // This seems to fix some strange problems with some Gui's, bug? -pw
  215. headerClip.extent.y = mHeaderDim.y;
  216. if (headerClip.intersect(clipRect))
  217. {
  218. GFX->setClipRect(headerClip);
  219. //now render the header
  220. onRenderColumnHeaders(offset, parentOffset, mHeaderDim);
  221. clipRect.point.y += headerClip.extent.y;
  222. clipRect.extent.y -= headerClip.extent.y;
  223. }
  224. offset.y += mHeaderDim.y;
  225. }
  226. //if we have row headings
  227. if (mHeaderDim.x > 0)
  228. {
  229. clipRect.point.x = getMax(clipRect.point.x, parentOffset.x + mHeaderDim.x);
  230. offset.x += mHeaderDim.x;
  231. }
  232. //save the original for clipping the row headers
  233. RectI origClipRect = clipRect;
  234. for (j = 0; j < mSize.y; j++)
  235. {
  236. //skip until we get to a visible row
  237. if ((j + 1) * mCellSize.y + offset.y < updateRect.point.y)
  238. continue;
  239. //break once we've reached the last visible row
  240. if(j * mCellSize.y + offset.y >= updateRect.point.y + updateRect.extent.y)
  241. break;
  242. //render the header
  243. if (mHeaderDim.x > 0)
  244. {
  245. headerClip.point.x = parentOffset.x;
  246. headerClip.extent.x = mHeaderDim.x;
  247. headerClip.point.y = offset.y + j * mCellSize.y;
  248. headerClip.extent.y = mCellSize.y;
  249. if (headerClip.intersect(origClipRect))
  250. {
  251. GFX->setClipRect(headerClip);
  252. //render the row header
  253. onRenderRowHeader(Point2I(0, offset.y + j * mCellSize.y),
  254. Point2I(parentOffset.x, offset.y + j * mCellSize.y),
  255. mHeaderDim, Point2I(0, j));
  256. }
  257. }
  258. //render the cells for the row
  259. for (i = 0; i < mSize.x; i++)
  260. {
  261. //skip past columns off the left edge
  262. if ((i + 1) * mCellSize.x + offset.x < updateRect.point.x)
  263. continue;
  264. //break once past the last visible column
  265. if (i * mCellSize.x + offset.x >= updateRect.point.x + updateRect.extent.x)
  266. break;
  267. S32 cellx = offset.x + i * mCellSize.x;
  268. S32 celly = offset.y + j * mCellSize.y;
  269. RectI cellClip(cellx, celly, mCellSize.x, mCellSize.y);
  270. //make sure the cell is within the update region
  271. if (cellClip.intersect(clipRect))
  272. {
  273. //set the clip rect
  274. GFX->setClipRect(cellClip);
  275. //render the cell
  276. onRenderCell(Point2I(cellx, celly), Point2I(i, j),
  277. i == mSelectedCell.x && j == mSelectedCell.y,
  278. i == mMouseOverCell.x && j == mMouseOverCell.y);
  279. }
  280. }
  281. }
  282. // Done rendering cells.
  283. // Render child controls, if any, on top.
  284. renderChildControls( inOffset, updateRect );
  285. }
  286. //-----------------------------------------------------------------------------
  287. void GuiArrayCtrl::onMouseDown( const GuiEvent &event )
  288. {
  289. if ( !mActive || !mAwake || !mVisible )
  290. return;
  291. }
  292. //-----------------------------------------------------------------------------
  293. void GuiArrayCtrl::onMouseUp( const GuiEvent &event )
  294. {
  295. if ( !mActive || !mAwake || !mVisible )
  296. return;
  297. //let the guiControl method take care of the rest
  298. Parent::onMouseUp(event);
  299. Point2I pt = globalToLocalCoord(event.mousePoint);
  300. pt.x -= mHeaderDim.x; pt.y -= mHeaderDim.y;
  301. Point2I cell(
  302. (pt.x < 0 ? -1 : pt.x / mCellSize.x),
  303. (pt.y < 0 ? -1 : pt.y / mCellSize.y)
  304. );
  305. if (cell.x >= 0 && cell.x < mSize.x && cell.y >= 0 && cell.y < mSize.y)
  306. {
  307. //store the previously selected cell
  308. Point2I prevSelected = mSelectedCell;
  309. //select the new cell
  310. cellSelected(Point2I(cell.x, cell.y));
  311. //if we double clicked on the *same* cell, evaluate the altConsole Command
  312. if ( ( event.mouseClickCount > 1 ) && ( prevSelected == mSelectedCell ) )
  313. execAltConsoleCallback();
  314. }
  315. }
  316. //-----------------------------------------------------------------------------
  317. void GuiArrayCtrl::onMouseEnter(const GuiEvent &event)
  318. {
  319. Point2I pt = globalToLocalCoord(event.mousePoint);
  320. pt.x -= mHeaderDim.x; pt.y -= mHeaderDim.y;
  321. //get the cell
  322. Point2I cell((pt.x < 0 ? -1 : pt.x / mCellSize.x), (pt.y < 0 ? -1 : pt.y / mCellSize.y));
  323. if (cell.x >= 0 && cell.x < mSize.x && cell.y >= 0 && cell.y < mSize.y)
  324. {
  325. mMouseOverCell = cell;
  326. setUpdateRegion(Point2I(cell.x * mCellSize.x + mHeaderDim.x,
  327. cell.y * mCellSize.y + mHeaderDim.y), mCellSize );
  328. onCellHighlighted(mMouseOverCell);
  329. }
  330. }
  331. //-----------------------------------------------------------------------------
  332. void GuiArrayCtrl::onMouseLeave(const GuiEvent & /*event*/)
  333. {
  334. setUpdateRegion(Point2I(mMouseOverCell.x * mCellSize.x + mHeaderDim.x,
  335. mMouseOverCell.y * mCellSize.y + mHeaderDim.y), mCellSize);
  336. mMouseOverCell.set(-1,-1);
  337. onCellHighlighted(mMouseOverCell);
  338. }
  339. //-----------------------------------------------------------------------------
  340. void GuiArrayCtrl::onMouseDragged(const GuiEvent &event)
  341. {
  342. // for the array control, the behavior of onMouseDragged is the same
  343. // as on mouse moved - basically just recalc the current mouse over cell
  344. // and set the update regions if necessary
  345. GuiArrayCtrl::onMouseMove(event);
  346. }
  347. //-----------------------------------------------------------------------------
  348. void GuiArrayCtrl::onMouseMove(const GuiEvent &event)
  349. {
  350. Point2I pt = globalToLocalCoord(event.mousePoint);
  351. pt.x -= mHeaderDim.x; pt.y -= mHeaderDim.y;
  352. Point2I cell((pt.x < 0 ? -1 : pt.x / mCellSize.x), (pt.y < 0 ? -1 : pt.y / mCellSize.y));
  353. if (cell.x != mMouseOverCell.x || cell.y != mMouseOverCell.y)
  354. {
  355. if (mMouseOverCell.x != -1)
  356. {
  357. setUpdateRegion(Point2I(mMouseOverCell.x * mCellSize.x + mHeaderDim.x,
  358. mMouseOverCell.y * mCellSize.y + mHeaderDim.y), mCellSize);
  359. }
  360. if (cell.x >= 0 && cell.x < mSize.x && cell.y >= 0 && cell.y < mSize.y)
  361. {
  362. setUpdateRegion(Point2I(cell.x * mCellSize.x + mHeaderDim.x,
  363. cell.y * mCellSize.y + mHeaderDim.y), mCellSize);
  364. mMouseOverCell = cell;
  365. }
  366. else
  367. mMouseOverCell.set(-1,-1);
  368. }
  369. onCellHighlighted(mMouseOverCell);
  370. }
  371. //-----------------------------------------------------------------------------
  372. bool GuiArrayCtrl::onKeyDown(const GuiEvent &event)
  373. {
  374. //if this control is a dead end, kill the event
  375. if ((! mVisible) || (! mActive) || (! mAwake)) return true;
  376. //get the parent
  377. S32 pageSize = 1;
  378. GuiControl *parent = getParent();
  379. if (parent && mCellSize.y > 0)
  380. {
  381. pageSize = getMax(1, (parent->getHeight() / mCellSize.y) - 1);
  382. }
  383. Point2I delta(0,0);
  384. switch (event.keyCode)
  385. {
  386. case KEY_LEFT:
  387. delta.set(-1, 0);
  388. break;
  389. case KEY_RIGHT:
  390. delta.set(1, 0);
  391. break;
  392. case KEY_UP:
  393. delta.set(0, -1);
  394. break;
  395. case KEY_DOWN:
  396. delta.set(0, 1);
  397. break;
  398. case KEY_PAGE_UP:
  399. delta.set(0, -pageSize);
  400. break;
  401. case KEY_PAGE_DOWN:
  402. delta.set(0, pageSize);
  403. break;
  404. case KEY_HOME:
  405. cellSelected( Point2I( 0, 0 ) );
  406. return( true );
  407. case KEY_END:
  408. cellSelected( Point2I( 0, mSize.y - 1 ) );
  409. return( true );
  410. default:
  411. return Parent::onKeyDown(event);
  412. }
  413. if (mSize.x < 1 || mSize.y < 1)
  414. return true;
  415. //select the first cell if no previous cell was selected
  416. if (mSelectedCell.x == -1 || mSelectedCell.y == -1)
  417. {
  418. cellSelected(Point2I(0,0));
  419. return true;
  420. }
  421. //select the cell
  422. Point2I cell = mSelectedCell;
  423. cell.x = getMax(0, getMin(mSize.x - 1, cell.x + delta.x));
  424. cell.y = getMax(0, getMin(mSize.y - 1, cell.y + delta.y));
  425. cellSelected(cell);
  426. return true;
  427. }
  428. //-----------------------------------------------------------------------------
  429. void GuiArrayCtrl::onRightMouseDown(const GuiEvent &event)
  430. {
  431. if ( !mActive || !mAwake || !mVisible )
  432. return;
  433. Parent::onRightMouseDown( event );
  434. Point2I cell;
  435. if( _findHitCell( event.mousePoint, cell ) )
  436. {
  437. char buf[32];
  438. dSprintf( buf, sizeof( buf ), "%d %d", event.mousePoint.x, event.mousePoint.y );
  439. // [rene, 21-Jan-11 ] clashes with callbacks defined in derived classes
  440. // Pass it to the console:
  441. Con::executef(this, "onRightMouseDown", Con::getIntArg(cell.x), Con::getIntArg(cell.y), buf);
  442. }
  443. }
  444. //-----------------------------------------------------------------------------
  445. bool GuiArrayCtrl::_findHitCell( const Point2I& pos, Point2I& cellOut )
  446. {
  447. Point2I pt = globalToLocalCoord( pos );
  448. pt.x -= mHeaderDim.x; pt.y -= mHeaderDim.y;
  449. Point2I cell( ( pt.x < 0 ? -1 : pt.x / mCellSize.x ), ( pt.y < 0 ? -1 : pt.y / mCellSize.y ) );
  450. if( cell.x >= 0 && cell.x < mSize.x && cell.y >= 0 && cell.y < mSize.y )
  451. {
  452. cellOut = cell;
  453. return true;
  454. }
  455. return false;
  456. }