2
0

guiButtonBaseCtrl.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588
  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/buttons/guiButtonBaseCtrl.h"
  23. #include "console/console.h"
  24. #include "console/engineAPI.h"
  25. #include "gfx/gfxDevice.h"
  26. #include "gui/core/guiCanvas.h"
  27. #include "i18n/lang.h"
  28. #include "sfx/sfxSystem.h"
  29. #include "sfx/sfxTrack.h"
  30. IMPLEMENT_CONOBJECT(GuiButtonBaseCtrl);
  31. ConsoleDocClass(GuiButtonBaseCtrl,
  32. "@brief The base class for the various button controls.\n\n"
  33. "This is the base class for the various types of button controls. If no more specific functionality is required than "
  34. "offered by this class, then it can be instantiated and used directly. Otherwise, its subclasses should be used:\n"
  35. "- GuiRadioCtrl (radio buttons)\n"
  36. "- GuiCheckBoxCtrl (checkboxes)\n"
  37. "- GuiButtonCtrl (push buttons with text labels)\n"
  38. "- GuiBitmapButtonCtrl (bitmapped buttons)\n"
  39. "- GuiBitmapButtonTextCtrl (bitmapped buttons with a text label)\n"
  40. "- GuiToggleButtonCtrl (toggle buttons, i.e. push buttons with \"sticky\" behavior)\n"
  41. "- GuiSwatchButtonCtrl (color swatch buttons)\n"
  42. "- GuiBorderButtonCtrl (push buttons for surrounding child controls)\n\n"
  43. "@ingroup GuiButtons"
  44. );
  45. IMPLEMENT_CALLBACK(GuiButtonBaseCtrl, onMouseDown, void, (), (),
  46. "If #useMouseEvents is true, this is called when the left mouse button is pressed on an (active) "
  47. "button.");
  48. IMPLEMENT_CALLBACK(GuiButtonBaseCtrl, onMouseUp, void, (), (),
  49. "If #useMouseEvents is true, this is called when the left mouse button is release over an (active) "
  50. "button.\n\n"
  51. "@note To trigger actions, better use onClick() since onMouseUp() will also be called when the mouse was "
  52. "not originally pressed on the button.");
  53. IMPLEMENT_CALLBACK(GuiButtonBaseCtrl, onClick, void, (), (),
  54. "Called when the primary action of the button is triggered (e.g. by a left mouse click).");
  55. IMPLEMENT_CALLBACK(GuiButtonBaseCtrl, onDoubleClick, void, (), (),
  56. "Called when the left mouse button is double-clicked on the button.");
  57. IMPLEMENT_CALLBACK(GuiButtonBaseCtrl, onRightClick, void, (), (),
  58. "Called when the right mouse button is clicked on the button.");
  59. IMPLEMENT_CALLBACK(GuiButtonBaseCtrl, onMouseEnter, void, (), (),
  60. "If #useMouseEvents is true, this is called when the mouse cursor moves over the button (only if the button "
  61. "is the front-most visible control, though).");
  62. IMPLEMENT_CALLBACK(GuiButtonBaseCtrl, onMouseLeave, void, (), (),
  63. "If #useMouseEvents is true, this is called when the mouse cursor moves off the button (only if the button "
  64. "had previously received an onMouseEvent() event).");
  65. IMPLEMENT_CALLBACK(GuiButtonBaseCtrl, onMouseDragged, void, (), (),
  66. "If #useMouseEvents is true, this is called when a left mouse button drag is detected, i.e. when the user "
  67. "pressed the left mouse button on the control and then moves the mouse over a certain distance threshold with "
  68. "the mouse button still pressed.");
  69. IMPLEMENT_CALLBACK(GuiButtonBaseCtrl, onHighlighted, void, (bool highlighted), (highlighted),
  70. "This is called when the highlighted state of the button is changed.");
  71. ImplementEnumType(GuiButtonType,
  72. "Type of button control.\n\n"
  73. "@ingroup GuiButtons")
  74. {
  75. GuiButtonBaseCtrl::ButtonTypePush, "PushButton", "A button that triggers an action when clicked."
  76. },
  77. { GuiButtonBaseCtrl::ButtonTypeCheck, "ToggleButton", "A button that is toggled between on and off state." },
  78. { GuiButtonBaseCtrl::ButtonTypeRadio, "RadioButton", "A button placed in groups for presenting choices." },
  79. EndImplementEnumType;
  80. //-----------------------------------------------------------------------------
  81. GuiButtonBaseCtrl::GuiButtonBaseCtrl()
  82. {
  83. mDepressed = false;
  84. mHighlighted = false;
  85. mActive = true;
  86. static StringTableEntry sButton = StringTable->insert("Button");
  87. mButtonText = sButton;
  88. mButtonTextID = StringTable->EmptyString();
  89. mStateOn = false;
  90. mRadioGroup = -1;
  91. mButtonType = ButtonTypePush;
  92. mUseMouseEvents = false;
  93. mMouseDragged = false;
  94. }
  95. //-----------------------------------------------------------------------------
  96. void GuiButtonBaseCtrl::initPersistFields()
  97. {
  98. docsURL;
  99. addGroup("Button");
  100. addField("text", TypeCaseString, Offset(mButtonText, GuiButtonBaseCtrl),
  101. "Text label to display on button (if button class supports text labels).");
  102. addField("textID", TypeString, Offset(mButtonTextID, GuiButtonBaseCtrl),
  103. "ID of string in string table to use for text label on button.\n\n"
  104. "@see setTextID\n"
  105. "@see GuiControl::langTableMod\n"
  106. "@see LangTable\n\n");
  107. addField("groupNum", TypeS32, Offset(mRadioGroup, GuiButtonBaseCtrl),
  108. "Radio button toggle group number. All radio buttons that are assigned the same #groupNum and that "
  109. "are parented to the same control will synchronize their toggle state, i.e. if one radio button is toggled on "
  110. "all other radio buttons in its group will be toggled off.\n\n"
  111. "The default group is -1.");
  112. addField("buttonType", TYPEID< ButtonType >(), Offset(mButtonType, GuiButtonBaseCtrl),
  113. "Button behavior type.\n");
  114. addField("useMouseEvents", TypeBool, Offset(mUseMouseEvents, GuiButtonBaseCtrl),
  115. "If true, mouse events will be passed on to script. Default is false.\n");
  116. endGroup("Button");
  117. Parent::initPersistFields();
  118. }
  119. //-----------------------------------------------------------------------------
  120. bool GuiButtonBaseCtrl::onWake()
  121. {
  122. if (!Parent::onWake())
  123. return false;
  124. // is we have a script variable, make sure we're in sync
  125. if (mConsoleVariable[0])
  126. mStateOn = Con::getBoolVariable(mConsoleVariable);
  127. if (mButtonTextID && *mButtonTextID != 0)
  128. setTextID(mButtonTextID);
  129. return true;
  130. }
  131. //-----------------------------------------------------------------------------
  132. void GuiButtonBaseCtrl::setText(const char* text)
  133. {
  134. mButtonText = StringTable->insert(text, true);
  135. }
  136. //-----------------------------------------------------------------------------
  137. void GuiButtonBaseCtrl::setTextID(const char* id)
  138. {
  139. S32 n = Con::getIntVariable(id, -1);
  140. if (n != -1)
  141. {
  142. mButtonTextID = StringTable->insert(id);
  143. setTextID(n);
  144. }
  145. }
  146. //-----------------------------------------------------------------------------
  147. void GuiButtonBaseCtrl::setTextID(S32 id)
  148. {
  149. const UTF8* str = getGUIString(id);
  150. if (str)
  151. setText((const char*)str);
  152. //mButtonTextID = id;
  153. }
  154. //-----------------------------------------------------------------------------
  155. const char* GuiButtonBaseCtrl::getText()
  156. {
  157. return mButtonText;
  158. }
  159. //-----------------------------------------------------------------------------
  160. void GuiButtonBaseCtrl::setStateOn(bool bStateOn)
  161. {
  162. if (!mActive)
  163. return;
  164. if (mButtonType == ButtonTypeCheck)
  165. {
  166. mStateOn = bStateOn;
  167. }
  168. else if (mButtonType == ButtonTypeRadio)
  169. {
  170. messageSiblings(mRadioGroup);
  171. mStateOn = bStateOn;
  172. }
  173. setUpdate();
  174. }
  175. //-----------------------------------------------------------------------------
  176. void GuiButtonBaseCtrl::acceleratorKeyPress(U32)
  177. {
  178. if (!mActive)
  179. return;
  180. //set the bool
  181. mDepressed = true;
  182. if (mProfile->mTabable)
  183. setFirstResponder();
  184. }
  185. //-----------------------------------------------------------------------------
  186. void GuiButtonBaseCtrl::acceleratorKeyRelease(U32)
  187. {
  188. if (!mActive)
  189. return;
  190. if (mDepressed)
  191. {
  192. //set the bool
  193. mDepressed = false;
  194. //perform the action
  195. onAction();
  196. }
  197. //update
  198. setUpdate();
  199. }
  200. //-----------------------------------------------------------------------------
  201. void GuiButtonBaseCtrl::onMouseDown(const GuiEvent& event)
  202. {
  203. if (!mActive)
  204. return;
  205. if (mProfile->mCanKeyFocus)
  206. setFirstResponder();
  207. if (mProfile->isSoundButtonDownValid())
  208. SFX->playOnce(mProfile->getSoundButtonDownProfile());
  209. mMouseDownPoint = event.mousePoint;
  210. mMouseDragged = false;
  211. if (mUseMouseEvents)
  212. onMouseDown_callback();
  213. //lock the mouse
  214. mouseLock();
  215. mDepressed = true;
  216. // If we have a double click then execute the alt command.
  217. if (event.mouseClickCount == 2)
  218. {
  219. onDoubleClick_callback();
  220. execAltConsoleCallback();
  221. }
  222. //update
  223. setUpdate();
  224. }
  225. //-----------------------------------------------------------------------------
  226. void GuiButtonBaseCtrl::onMouseEnter(const GuiEvent& event)
  227. {
  228. setUpdate();
  229. if (mUseMouseEvents)
  230. onMouseEnter_callback();
  231. if (isMouseLocked())
  232. {
  233. mDepressed = true;
  234. mHighlighted = true;
  235. onHighlighted_callback(mHighlighted);
  236. }
  237. else
  238. {
  239. if (mProfile->isSoundButtonOverValid())
  240. SFX->playOnce(mProfile->getSoundButtonOverProfile());
  241. mHighlighted = true;
  242. messageSiblings(mRadioGroup);
  243. onHighlighted_callback(mHighlighted);
  244. }
  245. }
  246. //-----------------------------------------------------------------------------
  247. void GuiButtonBaseCtrl::onMouseLeave(const GuiEvent&)
  248. {
  249. setUpdate();
  250. if (mUseMouseEvents)
  251. onMouseLeave_callback();
  252. if (isMouseLocked())
  253. mDepressed = false;
  254. mHighlighted = false;
  255. onHighlighted_callback(mHighlighted);
  256. messageSiblings(mRadioGroup);
  257. }
  258. //-----------------------------------------------------------------------------
  259. void GuiButtonBaseCtrl::onMouseUp(const GuiEvent& event)
  260. {
  261. mouseUnlock();
  262. if (!mActive)
  263. return;
  264. setUpdate();
  265. if (mUseMouseEvents)
  266. onMouseUp_callback();
  267. //if we released the mouse within this control, perform the action
  268. if (mDepressed)
  269. onAction();
  270. mDepressed = false;
  271. mMouseDragged = false;
  272. }
  273. //-----------------------------------------------------------------------------
  274. void GuiButtonBaseCtrl::onRightMouseUp(const GuiEvent& event)
  275. {
  276. onRightClick_callback();
  277. Parent::onRightMouseUp(event);
  278. }
  279. //-----------------------------------------------------------------------------
  280. void GuiButtonBaseCtrl::onMouseDragged(const GuiEvent& event)
  281. {
  282. if (mUseMouseEvents)
  283. {
  284. // If we haven't started a drag yet, find whether we have moved past
  285. // the tolerance value.
  286. if (!mMouseDragged)
  287. {
  288. Point2I delta = mMouseDownPoint - event.mousePoint;
  289. if (mAbs(delta.x) > 2 || mAbs(delta.y) > 2)
  290. mMouseDragged = true;
  291. }
  292. if (mMouseDragged)
  293. onMouseDragged_callback();
  294. }
  295. Parent::onMouseDragged(event);
  296. }
  297. //-----------------------------------------------------------------------------
  298. bool GuiButtonBaseCtrl::onKeyDown(const GuiEvent& event)
  299. {
  300. //if the control is a dead end, kill the event
  301. if (!mActive)
  302. return true;
  303. //see if the key down is a return or space or not
  304. if ((event.keyCode == KEY_RETURN || event.keyCode == KEY_SPACE)
  305. && event.modifier == 0)
  306. {
  307. if (mProfile->isSoundButtonDownValid())
  308. SFX->playOnce(mProfile->getSoundButtonDownProfile());
  309. return true;
  310. }
  311. //otherwise, pass the event to it's parent
  312. return Parent::onKeyDown(event);
  313. }
  314. //-----------------------------------------------------------------------------
  315. bool GuiButtonBaseCtrl::onKeyUp(const GuiEvent& event)
  316. {
  317. //if the control is a dead end, kill the event
  318. if (!mActive)
  319. return true;
  320. //see if the key down is a return or space or not
  321. if (mDepressed &&
  322. (event.keyCode == KEY_RETURN || event.keyCode == KEY_SPACE) &&
  323. event.modifier == 0)
  324. {
  325. onAction();
  326. return true;
  327. }
  328. //otherwise, pass the event to it's parent
  329. return Parent::onKeyUp(event);
  330. }
  331. //-----------------------------------------------------------------------------
  332. void GuiButtonBaseCtrl::setScriptValue(const char* value)
  333. {
  334. mStateOn = dAtob(value);
  335. // Update the console variable:
  336. if (mConsoleVariable[0])
  337. Con::setBoolVariable(mConsoleVariable, mStateOn);
  338. setUpdate();
  339. }
  340. //-----------------------------------------------------------------------------
  341. const char* GuiButtonBaseCtrl::getScriptValue()
  342. {
  343. return mStateOn ? "1" : "0";
  344. }
  345. //-----------------------------------------------------------------------------
  346. void GuiButtonBaseCtrl::onAction()
  347. {
  348. if (!mActive)
  349. return;
  350. if (mButtonType == ButtonTypeCheck)
  351. {
  352. mStateOn = mStateOn ? false : true;
  353. }
  354. else if (mButtonType == ButtonTypeRadio)
  355. {
  356. mStateOn = true;
  357. messageSiblings(mRadioGroup);
  358. }
  359. setUpdate();
  360. // Update the console variable:
  361. if (mConsoleVariable[0])
  362. Con::setBoolVariable(mConsoleVariable, mStateOn);
  363. onClick_callback();
  364. Parent::onAction();
  365. }
  366. //-----------------------------------------------------------------------------
  367. void GuiButtonBaseCtrl::onMessage(GuiControl* sender, S32 msg)
  368. {
  369. Parent::onMessage(sender, msg);
  370. if (mRadioGroup == msg)
  371. {
  372. if (mButtonType == ButtonTypeRadio)
  373. {
  374. setUpdate();
  375. mStateOn = (sender == this);
  376. // Update the console variable:
  377. if (mConsoleVariable[0])
  378. Con::setBoolVariable(mConsoleVariable, mStateOn);
  379. }
  380. else if (mButtonType == ButtonTypePush)
  381. {
  382. mHighlighted = (sender == this);
  383. onHighlighted_callback(mHighlighted);
  384. }
  385. }
  386. }
  387. void GuiButtonBaseCtrl::setHighlighted(bool highlighted)
  388. {
  389. mHighlighted = highlighted;
  390. onHighlighted_callback(mHighlighted);
  391. if (mRadioGroup != -1)
  392. {
  393. messageSiblings(mRadioGroup);
  394. }
  395. }
  396. //=============================================================================
  397. // Console Methods.
  398. //=============================================================================
  399. // MARK: ---- Console Methods ----
  400. //-----------------------------------------------------------------------------
  401. DefineEngineMethod(GuiButtonBaseCtrl, performClick, void, (), ,
  402. "Simulate a click on the button.\n"
  403. "This method will trigger the button's action just as if the button had been pressed by the "
  404. "user.\n\n")
  405. {
  406. object->onAction();
  407. }
  408. //-----------------------------------------------------------------------------
  409. DefineEngineMethod(GuiButtonBaseCtrl, setText, void, (const char* text), ,
  410. "Set the text displayed on the button's label.\n"
  411. "@param text The text to display as the button's text label.\n"
  412. "@note Not all buttons render text labels.\n\n"
  413. "@see getText\n"
  414. "@see setTextID\n")
  415. {
  416. object->setText(text);
  417. }
  418. //-----------------------------------------------------------------------------
  419. DefineEngineMethod(GuiButtonBaseCtrl, setTextID, void, (const char* id), ,
  420. "Set the text displayed on the button's label using a string from the string table "
  421. "assigned to the control.\n\n"
  422. "@param id Name of the variable that contains the integer string ID. Used to look up "
  423. "string in table.\n\n"
  424. "@note Not all buttons render text labels.\n\n"
  425. "@see setText\n"
  426. "@see getText\n"
  427. "@see GuiControl::langTableMod\n"
  428. "@see LangTable\n\n"
  429. "@ref Gui_i18n")
  430. {
  431. object->setTextID(id);
  432. }
  433. //-----------------------------------------------------------------------------
  434. DefineEngineMethod(GuiButtonBaseCtrl, getText, const char*, (), ,
  435. "Get the text display on the button's label (if any).\n\n"
  436. "@return The button's label.")
  437. {
  438. return object->getText();
  439. }
  440. //-----------------------------------------------------------------------------
  441. DefineEngineMethod(GuiButtonBaseCtrl, setStateOn, void, (bool isOn), (true),
  442. "For toggle or radio buttons, set whether the button is currently activated or not. For radio buttons, "
  443. "toggling a button on will toggle all other radio buttons in its group to off.\n\n"
  444. "@param isOn If true, the button will be toggled on (if not already); if false, it will be toggled off.\n\n"
  445. "@note Toggling the state of a button with this method will <em>not</em> not trigger the action associated with the "
  446. "button. To do that, use performClick().")
  447. {
  448. object->setStateOn(isOn);
  449. }
  450. //-----------------------------------------------------------------------------
  451. DefineEngineMethod(GuiButtonBaseCtrl, resetState, void, (), ,
  452. "Reset the mousing state of the button.\n\n"
  453. "This method should not generally be called.")
  454. {
  455. object->resetState();
  456. }
  457. DefineEngineMethod(GuiButtonBaseCtrl, setHighlighted, void, (bool highlighted), (false),
  458. "Reset the mousing state of the button.\n\n"
  459. "This method should not generally be called.")
  460. {
  461. object->setHighlighted(highlighted);
  462. }
  463. DefineEngineMethod(GuiButtonBaseCtrl, isHighlighted, bool, (), ,
  464. "Reset the mousing state of the button.\n\n"
  465. "This method should not generally be called.")
  466. {
  467. return object->isHighlighted();
  468. }