guiButtonBaseCtrl.cpp 17 KB

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