guiArrayCtrl.cpp 17 KB

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