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 "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.point.y + headerClip.extent.y - 1;
  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. }