guiSliderCtrl.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584
  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 "console/console.h"
  23. #include "console/consoleTypes.h"
  24. #include "console/engineAPI.h"
  25. #include "gfx/gfxTextureManager.h"
  26. #include "gui/controls/guiSliderCtrl.h"
  27. #include "gui/core/guiDefaultControlRender.h"
  28. #include "platform/event.h"
  29. #include "gfx/primBuilder.h"
  30. #include "gfx/gfxDrawUtil.h"
  31. #include "sfx/sfxSystem.h"
  32. #include "sfx/sfxTrack.h"
  33. IMPLEMENT_CONOBJECT( GuiSliderCtrl );
  34. ConsoleDocClass( GuiSliderCtrl,
  35. "@brief A control that displays a value between its minimal and maximal bounds using a slider placed on a vertical "
  36. "or horizontal axis.\n\n"
  37. "A slider displays a value and allows that value to be changed by dragging a thumb control along the axis of the "
  38. "slider. In this way, the value is changed between its allowed minimum and maximum.\n\n"
  39. "To hook up script code to the value changes of a slider, use the #command and #altCommand properties. #command is "
  40. "executed once the thumb is released by the user whereas #altCommand is called any time the slider value changes. "
  41. "When changing the slider value from script, however, trigger of #altCommand is suppressed by default.\n\n"
  42. "The orientation of a slider is automatically determined from the ratio of its width to its height. If a slider is "
  43. "taller than it is wide, it will be rendered with a vertical orientation. If it is wider than it is tall, it will be "
  44. "rendered with a horizontal orientation.\n\n"
  45. "The rendering of a slider depends on the bitmap in the slider's profile. This bitmap must be a bitmap array comprised "
  46. "of at least five bitmap rectangles. The rectangles are used such that:\n\n"
  47. "- Rectangle #1: Left edge of slider\n"
  48. "- Rectangle #2: Center piece of slider; this is stretched between the left and right edge\n"
  49. "- Rectangle #3: Right edge of slider\n"
  50. "- Rectangle #4: Thumb button in normal state\n"
  51. "- Rectangle #5: Thumb button in highlighted (mouse-over) state\n\n"
  52. "@tsexample\n"
  53. "// Create a sound source and a slider that changes the volume of the source.\n"
  54. "\n"
  55. "%source = sfxPlayOnce( \"art/sound/testing\", AudioLoop2D );\n"
  56. "\n"
  57. "new GuiSlider()\n"
  58. "{\n"
  59. " // Update the sound source volume when the slider is being dragged and released.\n"
  60. " command = %source @ \".setVolume( $ThisControl.value );\";\n"
  61. "\n"
  62. " // Limit the range to 0..1 since that is the allowable range for sound volumes.\n"
  63. " range = \"0 1\";\n"
  64. "};\n"
  65. "@endtsexample\n\n"
  66. "@see GuiTextEditSliderCtrl\n"
  67. "@see GuiTextEditSliderBitmapCtrl\n\n"
  68. "@ingroup GuiValues"
  69. );
  70. IMPLEMENT_CALLBACK( GuiSliderCtrl, onMouseDragged, void, (), (),
  71. "Called when the left mouse button is dragged across the slider." );
  72. //----------------------------------------------------------------------------
  73. GuiSliderCtrl::GuiSliderCtrl()
  74. : mRange( 0., 1.f ),
  75. mTicks( 10 ),
  76. mSnap( false ),
  77. mValue( 0.5f ),
  78. mThumbSize( 8, 20 ),
  79. mShiftPoint( 5 ),
  80. mShiftExtent( 10 ),
  81. mIncAmount( 0.f ),
  82. mDisplayValue( false ),
  83. mMouseOver( false ),
  84. mMouseDragged( false ),
  85. mDepressed( false )
  86. {
  87. }
  88. //----------------------------------------------------------------------------
  89. void GuiSliderCtrl::initPersistFields()
  90. {
  91. addGroup( "Slider" );
  92. addField( "range", TypePoint2F, Offset( mRange, GuiSliderCtrl ),
  93. "Min and max values corresponding to left and right slider position." );
  94. addField( "ticks", TypeS32, Offset( mTicks, GuiSliderCtrl ),
  95. "Spacing between tick marks in pixels. 0=off." );
  96. addField( "snap", TypeBool, Offset( mSnap, GuiSliderCtrl ),
  97. "Whether to snap the slider to tick marks." );
  98. addProtectedField( "value", TypeF32, Offset( mValue, GuiSliderCtrl ),
  99. _setValue, defaultProtectedGetFn,
  100. "The value corresponding to the current slider position." );
  101. endGroup( "Slider" );
  102. Parent::initPersistFields();
  103. }
  104. //----------------------------------------------------------------------------
  105. void GuiSliderCtrl::setValue(F32 val, bool doCallback)
  106. {
  107. _updateThumb( val, mSnap, false, doCallback );
  108. }
  109. //----------------------------------------------------------------------------
  110. void GuiSliderCtrl::setActive( bool value )
  111. {
  112. if( !value && mDepressed )
  113. {
  114. // We're in the middle of a drag. Finish it here as once we've
  115. // been deactivated, we are not going to see a mouse-up event.
  116. mDepressed = false;
  117. mouseUnlock();
  118. execConsoleCallback();
  119. }
  120. Parent::setActive( value );
  121. }
  122. //----------------------------------------------------------------------------
  123. bool GuiSliderCtrl::onWake()
  124. {
  125. if( !Parent::onWake() )
  126. return false;
  127. mHasTexture = mProfile->constructBitmapArray() >= NumBitmaps;
  128. if( mHasTexture )
  129. {
  130. mBitmapBounds = mProfile->mBitmapArrayRects.address();
  131. mThumbSize = Point2I( mBitmapBounds[ SliderButtonNormal ].extent.x, mBitmapBounds[ SliderButtonNormal ].extent.y );
  132. }
  133. F32 value;
  134. if( mConsoleVariable[ 0 ] )
  135. value = getFloatVariable();
  136. else
  137. value = mValue;
  138. mValue = mClampF( value, mRange.x, mRange.y );
  139. // mouse scroll increment percentage is 5% of the range
  140. mIncAmount = ( ( mRange.y - mRange.x ) * 0.05 );
  141. if( ( mThumbSize.y + mProfile->mFont->getHeight() - 4 ) <= getExtent().y )
  142. mDisplayValue = true;
  143. else
  144. mDisplayValue = false;
  145. _updateThumb( mValue, mSnap, true );
  146. return true;
  147. }
  148. //----------------------------------------------------------------------------
  149. void GuiSliderCtrl::onMouseDown(const GuiEvent &event)
  150. {
  151. if ( !mActive || !mAwake || !mVisible )
  152. return;
  153. mouseLock();
  154. setFirstResponder();
  155. mDepressed = true;
  156. Point2I curMousePos = globalToLocalCoord( event.mousePoint );
  157. F32 value;
  158. if (getWidth() >= getHeight())
  159. value = F32(curMousePos.x-mShiftPoint) / F32(getWidth()-mShiftExtent)*(mRange.y-mRange.x) + mRange.x;
  160. else
  161. value = F32(curMousePos.y) / F32(getHeight())*(mRange.y-mRange.x) + mRange.x;
  162. _updateThumb( value, mSnap || ( event.modifier & SI_SHIFT ) );
  163. }
  164. //----------------------------------------------------------------------------
  165. void GuiSliderCtrl::onMouseDragged( const GuiEvent &event )
  166. {
  167. if ( !mActive || !mAwake || !mVisible )
  168. return;
  169. mMouseDragged = true;
  170. F32 value = _getThumbValue( event );
  171. _updateThumb( value, mSnap || ( event.modifier & SI_SHIFT ) );
  172. onMouseDragged_callback();
  173. }
  174. //----------------------------------------------------------------------------
  175. void GuiSliderCtrl::onMouseUp( const GuiEvent& event )
  176. {
  177. if ( !mActive || !mAwake || !mVisible )
  178. return;
  179. mouseUnlock();
  180. mDepressed = false;
  181. mMouseDragged = false;
  182. _updateThumb( _getThumbValue( event ), event.modifier & SI_SHIFT );
  183. execConsoleCallback();
  184. }
  185. //----------------------------------------------------------------------------
  186. void GuiSliderCtrl::onMouseEnter(const GuiEvent &event)
  187. {
  188. setUpdate();
  189. if( isMouseLocked() )
  190. {
  191. mDepressed = true;
  192. mMouseOver = true;
  193. }
  194. else
  195. {
  196. if( mActive && mProfile->mSoundButtonOver )
  197. {
  198. //F32 pan = (F32(event.mousePoint.x)/F32(getRoot()->getWidth())*2.0f-1.0f)*0.8f;
  199. SFX->playOnce( mProfile->mSoundButtonOver );
  200. }
  201. mMouseOver = true;
  202. }
  203. }
  204. //----------------------------------------------------------------------------
  205. void GuiSliderCtrl::onMouseLeave(const GuiEvent &)
  206. {
  207. setUpdate();
  208. if( isMouseLocked() )
  209. mDepressed = false;
  210. mMouseOver = false;
  211. }
  212. //----------------------------------------------------------------------------
  213. bool GuiSliderCtrl::onMouseWheelUp(const GuiEvent &event)
  214. {
  215. if ( !mActive || !mAwake || !mVisible )
  216. return Parent::onMouseWheelUp(event);
  217. _updateThumb( mValue + mIncAmount, ( event.modifier & SI_SHIFT ) );
  218. execConsoleCallback();
  219. return true;
  220. }
  221. //----------------------------------------------------------------------------
  222. bool GuiSliderCtrl::onMouseWheelDown(const GuiEvent &event)
  223. {
  224. if ( !mActive || !mAwake || !mVisible )
  225. return Parent::onMouseWheelUp(event);
  226. _updateThumb( mValue - mIncAmount, ( event.modifier & SI_SHIFT ) );
  227. execConsoleCallback();
  228. return true;
  229. }
  230. //----------------------------------------------------------------------------
  231. void GuiSliderCtrl::_updateThumb( F32 _value, bool snap, bool onWake, bool doCallback )
  232. {
  233. if( snap && mTicks > 0 )
  234. {
  235. // If the shift key is held, snap to the nearest tick, if any are being drawn
  236. F32 tickStep = (mRange.y - mRange.x) / F32(mTicks + 1);
  237. F32 tickSteps = (_value - mRange.x) / tickStep;
  238. S32 actualTick = S32(tickSteps + 0.5);
  239. _value = actualTick * tickStep + mRange.x;
  240. }
  241. // Clamp the thumb to legal values.
  242. if( _value < mRange.x )
  243. _value = mRange.x;
  244. if( _value > mRange.y )
  245. _value = mRange.y;
  246. // If value hasn't changed and this isn't the initial update on
  247. // waking, do nothing.
  248. if( mValue == _value && !onWake )
  249. return;
  250. mValue = _value;
  251. Point2I ext = getExtent();
  252. ext.x -= ( mShiftExtent + mThumbSize.x ) / 2;
  253. // update the bounding thumb rect
  254. if (getWidth() >= getHeight())
  255. { // HORZ thumb
  256. S32 mx = (S32)((F32(ext.x) * (mValue-mRange.x) / (mRange.y-mRange.x)));
  257. S32 my = ext.y/2;
  258. if(mDisplayValue)
  259. my = mThumbSize.y/2;
  260. mThumb.point.x = mx - (mThumbSize.x/2);
  261. mThumb.point.y = my - (mThumbSize.y/2);
  262. mThumb.extent = mThumbSize;
  263. }
  264. else
  265. { // VERT thumb
  266. S32 mx = ext.x/2;
  267. S32 my = (S32)((F32(ext.y) * (mValue-mRange.x) / (mRange.y-mRange.x)));
  268. mThumb.point.x = mx - (mThumbSize.y/2);
  269. mThumb.point.y = my - (mThumbSize.x/2);
  270. mThumb.extent.x = mThumbSize.y;
  271. mThumb.extent.y = mThumbSize.x;
  272. }
  273. setFloatVariable(mValue);
  274. setUpdate();
  275. // Use the alt console command if you want to continually update:
  276. if ( !onWake && doCallback )
  277. execAltConsoleCallback();
  278. }
  279. //----------------------------------------------------------------------------
  280. void GuiSliderCtrl::onRender(Point2I offset, const RectI &updateRect)
  281. {
  282. Point2I pos(offset.x+mShiftPoint, offset.y);
  283. Point2I ext(getWidth() - mShiftExtent, getHeight());
  284. RectI thumb = mThumb;
  285. if( mHasTexture )
  286. {
  287. if(mTicks > 0)
  288. {
  289. // TODO: tick marks should be positioned based on the bitmap dimensions.
  290. Point2I mid(ext.x, ext.y/2);
  291. Point2I oldpos = pos;
  292. pos += Point2I(1, 0);
  293. PrimBuild::color4f( 0.f, 0.f, 0.f, 1.f );
  294. PrimBuild::begin( GFXLineList, ( mTicks + 2 ) * 2 );
  295. // tick marks
  296. for (U32 t = 0; t <= (mTicks+1); t++)
  297. {
  298. S32 x = (S32)(F32(mid.x+1)/F32(mTicks+1)*F32(t)) + pos.x;
  299. S32 y = pos.y + mid.y;
  300. PrimBuild::vertex2i(x, y + mShiftPoint);
  301. PrimBuild::vertex2i(x, y + mShiftPoint*2 + 2);
  302. }
  303. PrimBuild::end();
  304. // TODO: it would be nice, if the primitive builder were a little smarter,
  305. // so that we could change colors midstream.
  306. PrimBuild::color4f(0.9f, 0.9f, 0.9f, 1.0f);
  307. PrimBuild::begin( GFXLineList, ( mTicks + 2 ) * 2 );
  308. // tick marks
  309. for (U32 t = 0; t <= (mTicks+1); t++)
  310. {
  311. S32 x = (S32)(F32(mid.x+1)/F32(mTicks+1)*F32(t)) + pos.x + 1;
  312. S32 y = pos.y + mid.y + 1;
  313. PrimBuild::vertex2i(x, y + mShiftPoint );
  314. PrimBuild::vertex2i(x, y + mShiftPoint * 2 + 3);
  315. }
  316. PrimBuild::end();
  317. pos = oldpos;
  318. }
  319. S32 index = SliderButtonNormal;
  320. if(mMouseOver)
  321. index = SliderButtonHighlight;
  322. GFX->getDrawUtil()->clearBitmapModulation();
  323. //left border
  324. GFX->getDrawUtil()->drawBitmapSR(mProfile->mTextureObject, Point2I(offset.x,offset.y), mBitmapBounds[SliderLineLeft]);
  325. //right border
  326. GFX->getDrawUtil()->drawBitmapSR(mProfile->mTextureObject, Point2I(offset.x + getWidth() - mBitmapBounds[SliderLineRight].extent.x, offset.y), mBitmapBounds[SliderLineRight]);
  327. //draw our center piece to our slider control's border and stretch it
  328. RectI destRect;
  329. destRect.point.x = offset.x + mBitmapBounds[SliderLineLeft].extent.x;
  330. destRect.extent.x = getWidth() - mBitmapBounds[SliderLineLeft].extent.x - mBitmapBounds[SliderLineRight].extent.x;
  331. destRect.point.y = offset.y;
  332. destRect.extent.y = mBitmapBounds[SliderLineCenter].extent.y;
  333. RectI stretchRect;
  334. stretchRect = mBitmapBounds[SliderLineCenter];
  335. stretchRect.inset(1,0);
  336. GFX->getDrawUtil()->drawBitmapStretchSR(mProfile->mTextureObject, destRect, stretchRect);
  337. //draw our control slider button
  338. thumb.point += pos;
  339. GFX->getDrawUtil()->drawBitmapSR(mProfile->mTextureObject,Point2I(thumb.point.x,offset.y ),mBitmapBounds[index]);
  340. }
  341. else if (getWidth() >= getHeight())
  342. {
  343. Point2I mid(ext.x, ext.y/2);
  344. if(mDisplayValue)
  345. mid.set(ext.x, mThumbSize.y/2);
  346. PrimBuild::color4f( 0.f, 0.f, 0.f, 1.f );
  347. PrimBuild::begin( GFXLineList, ( mTicks + 2 ) * 2 + 2);
  348. // horz rule
  349. PrimBuild::vertex2i( pos.x, pos.y + mid.y );
  350. PrimBuild::vertex2i( pos.x + mid.x, pos.y + mid.y );
  351. // tick marks
  352. for( U32 t = 0; t <= ( mTicks + 1 ); t++ )
  353. {
  354. S32 x = (S32)( F32( mid.x - 1 ) / F32( mTicks + 1 ) * F32( t ) );
  355. PrimBuild::vertex2i( pos.x + x, pos.y + mid.y - mShiftPoint );
  356. PrimBuild::vertex2i( pos.x + x, pos.y + mid.y + mShiftPoint );
  357. }
  358. PrimBuild::end();
  359. }
  360. else
  361. {
  362. Point2I mid(ext.x/2, ext.y);
  363. PrimBuild::color4f( 0.f, 0.f, 0.f, 1.f );
  364. PrimBuild::begin( GFXLineList, ( mTicks + 2 ) * 2 + 2);
  365. // horz rule
  366. PrimBuild::vertex2i( pos.x + mid.x, pos.y );
  367. PrimBuild::vertex2i( pos.x + mid.x, pos.y + mid.y );
  368. // tick marks
  369. for( U32 t = 0; t <= ( mTicks + 1 ); t++ )
  370. {
  371. S32 y = (S32)( F32( mid.y - 1 ) / F32( mTicks + 1 ) * F32( t ) );
  372. PrimBuild::vertex2i( pos.x + mid.x - mShiftPoint, pos.y + y );
  373. PrimBuild::vertex2i( pos.x + mid.x + mShiftPoint, pos.y + y );
  374. }
  375. PrimBuild::end();
  376. mDisplayValue = false;
  377. }
  378. // draw the thumb
  379. thumb.point += pos;
  380. renderRaisedBox(thumb, mProfile);
  381. if(mDisplayValue)
  382. {
  383. char buf[20];
  384. dSprintf(buf,sizeof(buf),"%0.3f",mValue);
  385. Point2I textStart = thumb.point;
  386. S32 txt_w = mProfile->mFont->getStrWidth((const UTF8 *)buf);
  387. textStart.x += (S32)((thumb.extent.x/2.0f));
  388. textStart.y += thumb.extent.y - 2; //19
  389. textStart.x -= (txt_w/2);
  390. if(textStart.x < offset.x)
  391. textStart.x = offset.x;
  392. else if(textStart.x + txt_w > offset.x+getWidth())
  393. textStart.x -=((textStart.x + txt_w) - (offset.x+getWidth()));
  394. GFX->getDrawUtil()->setBitmapModulation(mProfile->mFontColor);
  395. GFX->getDrawUtil()->drawText(mProfile->mFont, textStart, buf, mProfile->mFontColors);
  396. }
  397. renderChildControls(offset, updateRect);
  398. }
  399. //----------------------------------------------------------------------------
  400. bool GuiSliderCtrl::resize( const Point2I& newPosition, const Point2I& newSize )
  401. {
  402. if( !Parent::resize( newPosition, newSize ) )
  403. return false;
  404. _updateThumb( mValue, false, true, false );
  405. return true;
  406. }
  407. //----------------------------------------------------------------------------
  408. void GuiSliderCtrl::parentResized( const RectI& oldParentRect, const RectI& newParentRect )
  409. {
  410. Parent::parentResized( oldParentRect, newParentRect );
  411. _updateThumb( mValue, false, true, false );
  412. }
  413. //----------------------------------------------------------------------------
  414. F32 GuiSliderCtrl::_getThumbValue( const GuiEvent& event )
  415. {
  416. Point2I curMousePos = globalToLocalCoord( event.mousePoint );
  417. F32 value;
  418. if( getWidth() >= getHeight() )
  419. value = F32( curMousePos.x - mShiftPoint ) / F32( getWidth() - mShiftExtent ) * ( mRange.y - mRange.x ) + mRange.x;
  420. else
  421. value = F32( curMousePos.y ) / F32( getHeight() ) * ( mRange.y - mRange.x ) + mRange.x;
  422. if(value > mRange.y )
  423. value = mRange.y;
  424. else if( value < mRange.x )
  425. value = mRange.x;
  426. if( mSnap || ( event.modifier & SI_SHIFT && mTicks >= 1 ) )
  427. {
  428. // If the shift key is held, snap to the nearest tick, if any are being drawn
  429. F32 tickStep = ( mRange.y - mRange.x ) / F32( mTicks + 1 );
  430. F32 tickSteps = (value - mRange.x ) / tickStep;
  431. S32 actualTick = S32( tickSteps + 0.5 );
  432. value = actualTick * tickStep + mRange.x;
  433. AssertFatal( value <= mRange.y && value >= mRange.x, "Error, out of bounds value generated from shift-snap of slider" );
  434. }
  435. return value;
  436. }
  437. //=============================================================================
  438. // Console Methods.
  439. //=============================================================================
  440. //-----------------------------------------------------------------------------
  441. DefineEngineMethod( GuiSliderCtrl, getValue, F32, (),,
  442. "Get the current value of the slider based on the position of the thumb.\n"
  443. "@return Slider position (from range.x to range.y)." )
  444. {
  445. return object->getValue();
  446. }
  447. //----------------------------------------------------------------------------
  448. DefineEngineMethod( GuiSliderCtrl, setValue, void, ( F32 pos, bool doCallback ), ( false ),
  449. "Set position of the thumb on the slider.\n"
  450. "@param pos New slider position (from range.x to range.y)\n"
  451. "@param doCallback If true, the altCommand callback will be invoked\n" )
  452. {
  453. object->setValue( pos, doCallback );
  454. }
  455. //----------------------------------------------------------------------------
  456. DefineEngineMethod( GuiSliderCtrl, isThumbBeingDragged, bool, (),,
  457. "Returns true if the thumb is currently being dragged by the user. This method is mainly useful "
  458. "for scrubbing type sliders where the slider position is sync'd to a changing value. When the "
  459. "user is dragging the thumb, however, the sync'ing should pause and not get in the way of the user." )
  460. {
  461. return object->isThumbBeingDragged();
  462. }