Audio plugin host https://kx.studio/carla
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1601 lines
58KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2013 - Raw Material Software Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. class Slider::Pimpl : public AsyncUpdater,
  18. public ButtonListener, // (can't use Button::Listener due to idiotic VC2005 bug)
  19. public LabelListener,
  20. public ValueListener
  21. {
  22. public:
  23. Pimpl (Slider& s, SliderStyle sliderStyle, TextEntryBoxPosition textBoxPosition)
  24. : owner (s),
  25. style (sliderStyle),
  26. lastCurrentValue (0), lastValueMin (0), lastValueMax (0),
  27. minimum (0), maximum (10), interval (0), doubleClickReturnValue (0),
  28. skewFactor (1.0), velocityModeSensitivity (1.0),
  29. velocityModeOffset (0.0), velocityModeThreshold (1),
  30. rotaryStart (float_Pi * 1.2f),
  31. rotaryEnd (float_Pi * 2.8f),
  32. sliderRegionStart (0), sliderRegionSize (1), sliderBeingDragged (-1),
  33. pixelsForFullDragExtent (250),
  34. textBoxPos (textBoxPosition),
  35. numDecimalPlaces (7),
  36. textBoxWidth (80), textBoxHeight (20),
  37. incDecButtonMode (incDecButtonsNotDraggable),
  38. editableText (true),
  39. doubleClickToValue (false),
  40. isVelocityBased (false),
  41. userKeyOverridesVelocity (true),
  42. rotaryStop (true),
  43. incDecButtonsSideBySide (false),
  44. sendChangeOnlyOnRelease (false),
  45. popupDisplayEnabled (false),
  46. menuEnabled (false),
  47. useDragEvents (false),
  48. scrollWheelEnabled (true),
  49. snapsToMousePos (true),
  50. parentForPopupDisplay (nullptr)
  51. {
  52. }
  53. ~Pimpl()
  54. {
  55. currentValue.removeListener (this);
  56. valueMin.removeListener (this);
  57. valueMax.removeListener (this);
  58. popupDisplay = nullptr;
  59. }
  60. //==============================================================================
  61. void registerListeners()
  62. {
  63. currentValue.addListener (this);
  64. valueMin.addListener (this);
  65. valueMax.addListener (this);
  66. }
  67. bool isHorizontal() const noexcept
  68. {
  69. return style == LinearHorizontal
  70. || style == LinearBar
  71. || style == TwoValueHorizontal
  72. || style == ThreeValueHorizontal;
  73. }
  74. bool isVertical() const noexcept
  75. {
  76. return style == LinearVertical
  77. || style == LinearBarVertical
  78. || style == TwoValueVertical
  79. || style == ThreeValueVertical;
  80. }
  81. bool isRotary() const noexcept
  82. {
  83. return style == Rotary
  84. || style == RotaryHorizontalDrag
  85. || style == RotaryVerticalDrag
  86. || style == RotaryHorizontalVerticalDrag;
  87. }
  88. bool incDecDragDirectionIsHorizontal() const noexcept
  89. {
  90. return incDecButtonMode == incDecButtonsDraggable_Horizontal
  91. || (incDecButtonMode == incDecButtonsDraggable_AutoDirection && incDecButtonsSideBySide);
  92. }
  93. float getPositionOfValue (const double value) const
  94. {
  95. if (isHorizontal() || isVertical())
  96. return getLinearSliderPos (value);
  97. jassertfalse; // not a valid call on a slider that doesn't work linearly!
  98. return 0.0f;
  99. }
  100. void setRange (const double newMin, const double newMax, const double newInt)
  101. {
  102. if (minimum != newMin || maximum != newMax || interval != newInt)
  103. {
  104. minimum = newMin;
  105. maximum = newMax;
  106. interval = newInt;
  107. // figure out the number of DPs needed to display all values at this
  108. // interval setting.
  109. numDecimalPlaces = 7;
  110. if (newInt != 0)
  111. {
  112. int v = std::abs (roundToInt (newInt * 10000000));
  113. while ((v % 10) == 0)
  114. {
  115. --numDecimalPlaces;
  116. v /= 10;
  117. }
  118. }
  119. // keep the current values inside the new range..
  120. if (style != TwoValueHorizontal && style != TwoValueVertical)
  121. {
  122. setValue (getValue(), dontSendNotification);
  123. }
  124. else
  125. {
  126. setMinValue (getMinValue(), dontSendNotification, false);
  127. setMaxValue (getMaxValue(), dontSendNotification, false);
  128. }
  129. updateText();
  130. }
  131. }
  132. double getValue() const
  133. {
  134. // for a two-value style slider, you should use the getMinValue() and getMaxValue()
  135. // methods to get the two values.
  136. jassert (style != TwoValueHorizontal && style != TwoValueVertical);
  137. return currentValue.getValue();
  138. }
  139. void setValue (double newValue, const NotificationType notification)
  140. {
  141. // for a two-value style slider, you should use the setMinValue() and setMaxValue()
  142. // methods to set the two values.
  143. jassert (style != TwoValueHorizontal && style != TwoValueVertical);
  144. newValue = constrainedValue (newValue);
  145. if (style == ThreeValueHorizontal || style == ThreeValueVertical)
  146. {
  147. jassert ((double) valueMin.getValue() <= (double) valueMax.getValue());
  148. newValue = jlimit ((double) valueMin.getValue(),
  149. (double) valueMax.getValue(),
  150. newValue);
  151. }
  152. if (newValue != lastCurrentValue)
  153. {
  154. if (valueBox != nullptr)
  155. valueBox->hideEditor (true);
  156. lastCurrentValue = newValue;
  157. // (need to do this comparison because the Value will use equalsWithSameType to compare
  158. // the new and old values, so will generate unwanted change events if the type changes)
  159. if (currentValue != newValue)
  160. currentValue = newValue;
  161. updateText();
  162. owner.repaint();
  163. if (popupDisplay != nullptr)
  164. popupDisplay->updatePosition (owner.getTextFromValue (newValue));
  165. triggerChangeMessage (notification);
  166. }
  167. }
  168. void setMinValue (double newValue, const NotificationType notification,
  169. const bool allowNudgingOfOtherValues)
  170. {
  171. // The minimum value only applies to sliders that are in two- or three-value mode.
  172. jassert (style == TwoValueHorizontal || style == TwoValueVertical
  173. || style == ThreeValueHorizontal || style == ThreeValueVertical);
  174. newValue = constrainedValue (newValue);
  175. if (style == TwoValueHorizontal || style == TwoValueVertical)
  176. {
  177. if (allowNudgingOfOtherValues && newValue > (double) valueMax.getValue())
  178. setMaxValue (newValue, notification, false);
  179. newValue = jmin ((double) valueMax.getValue(), newValue);
  180. }
  181. else
  182. {
  183. if (allowNudgingOfOtherValues && newValue > lastCurrentValue)
  184. setValue (newValue, notification);
  185. newValue = jmin (lastCurrentValue, newValue);
  186. }
  187. if (lastValueMin != newValue)
  188. {
  189. lastValueMin = newValue;
  190. valueMin = newValue;
  191. owner.repaint();
  192. if (popupDisplay != nullptr)
  193. popupDisplay->updatePosition (owner.getTextFromValue (newValue));
  194. triggerChangeMessage (notification);
  195. }
  196. }
  197. void setMaxValue (double newValue, const NotificationType notification,
  198. const bool allowNudgingOfOtherValues)
  199. {
  200. // The maximum value only applies to sliders that are in two- or three-value mode.
  201. jassert (style == TwoValueHorizontal || style == TwoValueVertical
  202. || style == ThreeValueHorizontal || style == ThreeValueVertical);
  203. newValue = constrainedValue (newValue);
  204. if (style == TwoValueHorizontal || style == TwoValueVertical)
  205. {
  206. if (allowNudgingOfOtherValues && newValue < (double) valueMin.getValue())
  207. setMinValue (newValue, notification, false);
  208. newValue = jmax ((double) valueMin.getValue(), newValue);
  209. }
  210. else
  211. {
  212. if (allowNudgingOfOtherValues && newValue < lastCurrentValue)
  213. setValue (newValue, notification);
  214. newValue = jmax (lastCurrentValue, newValue);
  215. }
  216. if (lastValueMax != newValue)
  217. {
  218. lastValueMax = newValue;
  219. valueMax = newValue;
  220. owner.repaint();
  221. if (popupDisplay != nullptr)
  222. popupDisplay->updatePosition (owner.getTextFromValue (valueMax.getValue()));
  223. triggerChangeMessage (notification);
  224. }
  225. }
  226. void setMinAndMaxValues (double newMinValue, double newMaxValue, const NotificationType notification)
  227. {
  228. // The maximum value only applies to sliders that are in two- or three-value mode.
  229. jassert (style == TwoValueHorizontal || style == TwoValueVertical
  230. || style == ThreeValueHorizontal || style == ThreeValueVertical);
  231. if (newMaxValue < newMinValue)
  232. std::swap (newMaxValue, newMinValue);
  233. newMinValue = constrainedValue (newMinValue);
  234. newMaxValue = constrainedValue (newMaxValue);
  235. if (lastValueMax != newMaxValue || lastValueMin != newMinValue)
  236. {
  237. lastValueMax = newMaxValue;
  238. lastValueMin = newMinValue;
  239. valueMin = newMinValue;
  240. valueMax = newMaxValue;
  241. owner.repaint();
  242. triggerChangeMessage (notification);
  243. }
  244. }
  245. double getMinValue() const
  246. {
  247. // The minimum value only applies to sliders that are in two- or three-value mode.
  248. jassert (style == TwoValueHorizontal || style == TwoValueVertical
  249. || style == ThreeValueHorizontal || style == ThreeValueVertical);
  250. return valueMin.getValue();
  251. }
  252. double getMaxValue() const
  253. {
  254. // The maximum value only applies to sliders that are in two- or three-value mode.
  255. jassert (style == TwoValueHorizontal || style == TwoValueVertical
  256. || style == ThreeValueHorizontal || style == ThreeValueVertical);
  257. return valueMax.getValue();
  258. }
  259. void triggerChangeMessage (const NotificationType notification)
  260. {
  261. if (notification != dontSendNotification)
  262. {
  263. if (notification == sendNotificationSync)
  264. handleAsyncUpdate();
  265. else
  266. triggerAsyncUpdate();
  267. owner.valueChanged();
  268. }
  269. }
  270. void handleAsyncUpdate() override
  271. {
  272. cancelPendingUpdate();
  273. Component::BailOutChecker checker (&owner);
  274. Slider* slider = &owner; // (must use an intermediate variable here to avoid a VS2005 compiler bug)
  275. listeners.callChecked (checker, &SliderListener::sliderValueChanged, slider); // (can't use Slider::Listener due to idiotic VC2005 bug)
  276. }
  277. void sendDragStart()
  278. {
  279. owner.startedDragging();
  280. Component::BailOutChecker checker (&owner);
  281. Slider* slider = &owner; // (must use an intermediate variable here to avoid a VS2005 compiler bug)
  282. listeners.callChecked (checker, &SliderListener::sliderDragStarted, slider);
  283. }
  284. void sendDragEnd()
  285. {
  286. owner.stoppedDragging();
  287. sliderBeingDragged = -1;
  288. Component::BailOutChecker checker (&owner);
  289. Slider* slider = &owner; // (must use an intermediate variable here to avoid a VS2005 compiler bug)
  290. listeners.callChecked (checker, &SliderListener::sliderDragEnded, slider);
  291. }
  292. struct DragInProgress
  293. {
  294. DragInProgress (Pimpl& p) : owner (p) { owner.sendDragStart(); }
  295. ~DragInProgress() { owner.sendDragEnd(); }
  296. Pimpl& owner;
  297. JUCE_DECLARE_NON_COPYABLE (DragInProgress)
  298. };
  299. void buttonClicked (Button* button) override
  300. {
  301. if (style == IncDecButtons)
  302. {
  303. const double delta = (button == incButton) ? interval : -interval;
  304. DragInProgress drag (*this);
  305. setValue (owner.snapValue (getValue() + delta, notDragging), sendNotificationSync);
  306. }
  307. }
  308. void valueChanged (Value& value) override
  309. {
  310. if (value.refersToSameSourceAs (currentValue))
  311. {
  312. if (style != TwoValueHorizontal && style != TwoValueVertical)
  313. setValue (currentValue.getValue(), dontSendNotification);
  314. }
  315. else if (value.refersToSameSourceAs (valueMin))
  316. setMinValue (valueMin.getValue(), dontSendNotification, true);
  317. else if (value.refersToSameSourceAs (valueMax))
  318. setMaxValue (valueMax.getValue(), dontSendNotification, true);
  319. }
  320. void labelTextChanged (Label* label) override
  321. {
  322. const double newValue = owner.snapValue (owner.getValueFromText (label->getText()), notDragging);
  323. if (newValue != (double) currentValue.getValue())
  324. {
  325. DragInProgress drag (*this);
  326. setValue (newValue, sendNotificationSync);
  327. }
  328. updateText(); // force a clean-up of the text, needed in case setValue() hasn't done this.
  329. }
  330. void updateText()
  331. {
  332. if (valueBox != nullptr)
  333. {
  334. String newValue (owner.getTextFromValue (currentValue.getValue()));
  335. if (newValue != valueBox->getText())
  336. valueBox->setText (newValue, dontSendNotification);
  337. }
  338. }
  339. double constrainedValue (double value) const
  340. {
  341. if (interval > 0)
  342. value = minimum + interval * std::floor ((value - minimum) / interval + 0.5);
  343. if (value <= minimum || maximum <= minimum)
  344. value = minimum;
  345. else if (value >= maximum)
  346. value = maximum;
  347. return value;
  348. }
  349. float getLinearSliderPos (const double value) const
  350. {
  351. double pos;
  352. if (maximum <= minimum)
  353. pos = 0.5;
  354. else if (value < minimum)
  355. pos = 0.0;
  356. else if (value > maximum)
  357. pos = 1.0;
  358. else
  359. pos = owner.valueToProportionOfLength (value);
  360. if (isVertical() || style == IncDecButtons)
  361. pos = 1.0 - pos;
  362. jassert (pos >= 0 && pos <= 1.0);
  363. return (float) (sliderRegionStart + pos * sliderRegionSize);
  364. }
  365. void setSliderStyle (const SliderStyle newStyle)
  366. {
  367. if (style != newStyle)
  368. {
  369. style = newStyle;
  370. owner.repaint();
  371. owner.lookAndFeelChanged();
  372. }
  373. }
  374. void setRotaryParameters (const float startAngleRadians,
  375. const float endAngleRadians,
  376. const bool stopAtEnd)
  377. {
  378. // make sure the values are sensible..
  379. jassert (rotaryStart >= 0 && rotaryEnd >= 0);
  380. jassert (rotaryStart < float_Pi * 4.0f && rotaryEnd < float_Pi * 4.0f);
  381. jassert (rotaryStart < rotaryEnd);
  382. rotaryStart = startAngleRadians;
  383. rotaryEnd = endAngleRadians;
  384. rotaryStop = stopAtEnd;
  385. }
  386. void setVelocityModeParameters (const double sensitivity, const int threshold,
  387. const double offset, const bool userCanPressKeyToSwapMode)
  388. {
  389. velocityModeSensitivity = sensitivity;
  390. velocityModeOffset = offset;
  391. velocityModeThreshold = threshold;
  392. userKeyOverridesVelocity = userCanPressKeyToSwapMode;
  393. }
  394. void setSkewFactorFromMidPoint (const double sliderValueToShowAtMidPoint)
  395. {
  396. if (maximum > minimum)
  397. skewFactor = log (0.5) / log ((sliderValueToShowAtMidPoint - minimum)
  398. / (maximum - minimum));
  399. }
  400. void setIncDecButtonsMode (const IncDecButtonMode mode)
  401. {
  402. if (incDecButtonMode != mode)
  403. {
  404. incDecButtonMode = mode;
  405. owner.lookAndFeelChanged();
  406. }
  407. }
  408. void setTextBoxStyle (const TextEntryBoxPosition newPosition,
  409. const bool isReadOnly,
  410. const int textEntryBoxWidth,
  411. const int textEntryBoxHeight)
  412. {
  413. if (textBoxPos != newPosition
  414. || editableText != (! isReadOnly)
  415. || textBoxWidth != textEntryBoxWidth
  416. || textBoxHeight != textEntryBoxHeight)
  417. {
  418. textBoxPos = newPosition;
  419. editableText = ! isReadOnly;
  420. textBoxWidth = textEntryBoxWidth;
  421. textBoxHeight = textEntryBoxHeight;
  422. owner.repaint();
  423. owner.lookAndFeelChanged();
  424. }
  425. }
  426. void setTextBoxIsEditable (const bool shouldBeEditable)
  427. {
  428. editableText = shouldBeEditable;
  429. if (valueBox != nullptr)
  430. valueBox->setEditable (shouldBeEditable && owner.isEnabled());
  431. }
  432. void showTextBox()
  433. {
  434. jassert (editableText); // this should probably be avoided in read-only sliders.
  435. if (valueBox != nullptr)
  436. valueBox->showEditor();
  437. }
  438. void hideTextBox (const bool discardCurrentEditorContents)
  439. {
  440. if (valueBox != nullptr)
  441. {
  442. valueBox->hideEditor (discardCurrentEditorContents);
  443. if (discardCurrentEditorContents)
  444. updateText();
  445. }
  446. }
  447. void setTextValueSuffix (const String& suffix)
  448. {
  449. if (textSuffix != suffix)
  450. {
  451. textSuffix = suffix;
  452. updateText();
  453. }
  454. }
  455. void lookAndFeelChanged (LookAndFeel& lf)
  456. {
  457. if (textBoxPos != NoTextBox)
  458. {
  459. const String previousTextBoxContent (valueBox != nullptr ? valueBox->getText()
  460. : owner.getTextFromValue (currentValue.getValue()));
  461. valueBox = nullptr;
  462. owner.addAndMakeVisible (valueBox = lf.createSliderTextBox (owner));
  463. valueBox->setWantsKeyboardFocus (false);
  464. valueBox->setText (previousTextBoxContent, dontSendNotification);
  465. if (valueBox->isEditable() != editableText) // (avoid overriding the single/double click flags unless we have to)
  466. valueBox->setEditable (editableText && owner.isEnabled());
  467. valueBox->addListener (this);
  468. if (style == LinearBar || style == LinearBarVertical)
  469. {
  470. valueBox->addMouseListener (&owner, false);
  471. valueBox->setMouseCursor (MouseCursor::ParentCursor);
  472. }
  473. else
  474. {
  475. valueBox->setTooltip (owner.getTooltip());
  476. }
  477. }
  478. else
  479. {
  480. valueBox = nullptr;
  481. }
  482. if (style == IncDecButtons)
  483. {
  484. owner.addAndMakeVisible (incButton = lf.createSliderButton (owner, true));
  485. incButton->addListener (this);
  486. owner.addAndMakeVisible (decButton = lf.createSliderButton (owner, false));
  487. decButton->addListener (this);
  488. if (incDecButtonMode != incDecButtonsNotDraggable)
  489. {
  490. incButton->addMouseListener (&owner, false);
  491. decButton->addMouseListener (&owner, false);
  492. }
  493. else
  494. {
  495. incButton->setRepeatSpeed (300, 100, 20);
  496. decButton->setRepeatSpeed (300, 100, 20);
  497. }
  498. const String tooltip (owner.getTooltip());
  499. incButton->setTooltip (tooltip);
  500. decButton->setTooltip (tooltip);
  501. }
  502. else
  503. {
  504. incButton = nullptr;
  505. decButton = nullptr;
  506. }
  507. owner.setComponentEffect (lf.getSliderEffect (owner));
  508. owner.resized();
  509. owner.repaint();
  510. }
  511. void showPopupMenu()
  512. {
  513. PopupMenu m;
  514. m.setLookAndFeel (&owner.getLookAndFeel());
  515. m.addItem (1, TRANS ("Velocity-sensitive mode"), true, isVelocityBased);
  516. m.addSeparator();
  517. if (isRotary())
  518. {
  519. PopupMenu rotaryMenu;
  520. rotaryMenu.addItem (2, TRANS ("Use circular dragging"), true, style == Rotary);
  521. rotaryMenu.addItem (3, TRANS ("Use left-right dragging"), true, style == RotaryHorizontalDrag);
  522. rotaryMenu.addItem (4, TRANS ("Use up-down dragging"), true, style == RotaryVerticalDrag);
  523. rotaryMenu.addItem (5, TRANS ("Use left-right/up-down dragging"), true, style == RotaryHorizontalVerticalDrag);
  524. m.addSubMenu (TRANS ("Rotary mode"), rotaryMenu);
  525. }
  526. m.showMenuAsync (PopupMenu::Options(),
  527. ModalCallbackFunction::forComponent (sliderMenuCallback, &owner));
  528. }
  529. static void sliderMenuCallback (const int result, Slider* slider)
  530. {
  531. if (slider != nullptr)
  532. {
  533. switch (result)
  534. {
  535. case 1: slider->setVelocityBasedMode (! slider->getVelocityBasedMode()); break;
  536. case 2: slider->setSliderStyle (Rotary); break;
  537. case 3: slider->setSliderStyle (RotaryHorizontalDrag); break;
  538. case 4: slider->setSliderStyle (RotaryVerticalDrag); break;
  539. case 5: slider->setSliderStyle (RotaryHorizontalVerticalDrag); break;
  540. default: break;
  541. }
  542. }
  543. }
  544. int getThumbIndexAt (const MouseEvent& e)
  545. {
  546. const bool isTwoValue = (style == TwoValueHorizontal || style == TwoValueVertical);
  547. const bool isThreeValue = (style == ThreeValueHorizontal || style == ThreeValueVertical);
  548. if (isTwoValue || isThreeValue)
  549. {
  550. const float mousePos = (float) (isVertical() ? e.y : e.x);
  551. const float normalPosDistance = std::abs (getLinearSliderPos (currentValue.getValue()) - mousePos);
  552. const float minPosDistance = std::abs (getLinearSliderPos (valueMin.getValue()) - 0.1f - mousePos);
  553. const float maxPosDistance = std::abs (getLinearSliderPos (valueMax.getValue()) + 0.1f - mousePos);
  554. if (isTwoValue)
  555. return maxPosDistance <= minPosDistance ? 2 : 1;
  556. if (normalPosDistance >= minPosDistance && maxPosDistance >= minPosDistance)
  557. return 1;
  558. if (normalPosDistance >= maxPosDistance)
  559. return 2;
  560. }
  561. return 0;
  562. }
  563. //==============================================================================
  564. void handleRotaryDrag (const MouseEvent& e)
  565. {
  566. const int dx = e.x - sliderRect.getCentreX();
  567. const int dy = e.y - sliderRect.getCentreY();
  568. if (dx * dx + dy * dy > 25)
  569. {
  570. double angle = std::atan2 ((double) dx, (double) -dy);
  571. while (angle < 0.0)
  572. angle += double_Pi * 2.0;
  573. if (rotaryStop && ! e.mouseWasClicked())
  574. {
  575. if (std::abs (angle - lastAngle) > double_Pi)
  576. {
  577. if (angle >= lastAngle)
  578. angle -= double_Pi * 2.0;
  579. else
  580. angle += double_Pi * 2.0;
  581. }
  582. if (angle >= lastAngle)
  583. angle = jmin (angle, (double) jmax (rotaryStart, rotaryEnd));
  584. else
  585. angle = jmax (angle, (double) jmin (rotaryStart, rotaryEnd));
  586. }
  587. else
  588. {
  589. while (angle < rotaryStart)
  590. angle += double_Pi * 2.0;
  591. if (angle > rotaryEnd)
  592. {
  593. if (smallestAngleBetween (angle, rotaryStart)
  594. <= smallestAngleBetween (angle, rotaryEnd))
  595. angle = rotaryStart;
  596. else
  597. angle = rotaryEnd;
  598. }
  599. }
  600. const double proportion = (angle - rotaryStart) / (rotaryEnd - rotaryStart);
  601. valueWhenLastDragged = owner.proportionOfLengthToValue (jlimit (0.0, 1.0, proportion));
  602. lastAngle = angle;
  603. }
  604. }
  605. void handleAbsoluteDrag (const MouseEvent& e)
  606. {
  607. const int mousePos = (isHorizontal() || style == RotaryHorizontalDrag) ? e.x : e.y;
  608. double newPos = (mousePos - sliderRegionStart) / (double) sliderRegionSize;
  609. if (style == RotaryHorizontalDrag
  610. || style == RotaryVerticalDrag
  611. || style == IncDecButtons
  612. || ((style == LinearHorizontal || style == LinearVertical || style == LinearBar || style == LinearBarVertical)
  613. && ! snapsToMousePos))
  614. {
  615. const float mouseDiff = (style == RotaryHorizontalDrag
  616. || style == LinearHorizontal
  617. || style == LinearBar
  618. || (style == IncDecButtons && incDecDragDirectionIsHorizontal()))
  619. ? e.position.x - mouseDragStartPos.x
  620. : mouseDragStartPos.y - e.position.y;
  621. newPos = owner.valueToProportionOfLength (valueOnMouseDown)
  622. + mouseDiff * (1.0 / pixelsForFullDragExtent);
  623. if (style == IncDecButtons)
  624. {
  625. incButton->setState (mouseDiff < 0 ? Button::buttonNormal : Button::buttonDown);
  626. decButton->setState (mouseDiff > 0 ? Button::buttonNormal : Button::buttonDown);
  627. }
  628. }
  629. else if (style == RotaryHorizontalVerticalDrag)
  630. {
  631. const float mouseDiff = (e.position.x - mouseDragStartPos.x)
  632. + (mouseDragStartPos.y - e.position.y);
  633. newPos = owner.valueToProportionOfLength (valueOnMouseDown)
  634. + mouseDiff * (1.0 / pixelsForFullDragExtent);
  635. }
  636. else
  637. {
  638. if (isVertical())
  639. newPos = 1.0 - newPos;
  640. }
  641. valueWhenLastDragged = owner.proportionOfLengthToValue (jlimit (0.0, 1.0, newPos));
  642. }
  643. void handleVelocityDrag (const MouseEvent& e)
  644. {
  645. const float mouseDiff = style == RotaryHorizontalVerticalDrag
  646. ? (e.x - mousePosWhenLastDragged.x) + (mousePosWhenLastDragged.y - e.y)
  647. : (isHorizontal()
  648. || style == RotaryHorizontalDrag
  649. || (style == IncDecButtons && incDecDragDirectionIsHorizontal()))
  650. ? e.position.x - mousePosWhenLastDragged.x
  651. : e.position.y - mousePosWhenLastDragged.y;
  652. const double maxSpeed = jmax (200, sliderRegionSize);
  653. double speed = jlimit (0.0, maxSpeed, (double) abs (mouseDiff));
  654. if (speed != 0)
  655. {
  656. speed = 0.2 * velocityModeSensitivity
  657. * (1.0 + std::sin (double_Pi * (1.5 + jmin (0.5, velocityModeOffset
  658. + jmax (0.0, (double) (speed - velocityModeThreshold))
  659. / maxSpeed))));
  660. if (mouseDiff < 0)
  661. speed = -speed;
  662. if (isVertical() || style == RotaryVerticalDrag
  663. || (style == IncDecButtons && ! incDecDragDirectionIsHorizontal()))
  664. speed = -speed;
  665. const double currentPos = owner.valueToProportionOfLength (valueWhenLastDragged);
  666. valueWhenLastDragged = owner.proportionOfLengthToValue (jlimit (0.0, 1.0, currentPos + speed));
  667. e.source.enableUnboundedMouseMovement (true, false);
  668. }
  669. }
  670. void mouseDown (const MouseEvent& e)
  671. {
  672. incDecDragged = false;
  673. useDragEvents = false;
  674. mouseDragStartPos = mousePosWhenLastDragged = e.position;
  675. currentDrag = nullptr;
  676. if (owner.isEnabled())
  677. {
  678. if (e.mods.isPopupMenu() && menuEnabled)
  679. {
  680. showPopupMenu();
  681. }
  682. else if (canDoubleClickToValue() && e.mods.isAltDown())
  683. {
  684. mouseDoubleClick();
  685. }
  686. else if (maximum > minimum)
  687. {
  688. useDragEvents = true;
  689. if (valueBox != nullptr)
  690. valueBox->hideEditor (true);
  691. sliderBeingDragged = getThumbIndexAt (e);
  692. minMaxDiff = (double) valueMax.getValue() - (double) valueMin.getValue();
  693. lastAngle = rotaryStart + (rotaryEnd - rotaryStart)
  694. * owner.valueToProportionOfLength (currentValue.getValue());
  695. valueWhenLastDragged = (sliderBeingDragged == 2 ? valueMax
  696. : (sliderBeingDragged == 1 ? valueMin
  697. : currentValue)).getValue();
  698. valueOnMouseDown = valueWhenLastDragged;
  699. if (popupDisplayEnabled)
  700. {
  701. PopupDisplayComponent* const popup = new PopupDisplayComponent (owner);
  702. popupDisplay = popup;
  703. if (parentForPopupDisplay != nullptr)
  704. parentForPopupDisplay->addChildComponent (popup);
  705. else
  706. popup->addToDesktop (0);
  707. popup->setVisible (true);
  708. }
  709. currentDrag = new DragInProgress (*this);
  710. mouseDrag (e);
  711. }
  712. }
  713. }
  714. void mouseDrag (const MouseEvent& e)
  715. {
  716. if (useDragEvents && maximum > minimum
  717. && ! ((style == LinearBar || style == LinearBarVertical)
  718. && e.mouseWasClicked() && valueBox != nullptr && valueBox->isEditable()))
  719. {
  720. DragMode dragMode = notDragging;
  721. if (style == Rotary)
  722. {
  723. handleRotaryDrag (e);
  724. }
  725. else
  726. {
  727. if (style == IncDecButtons && ! incDecDragged)
  728. {
  729. if (e.getDistanceFromDragStart() < 10 || e.mouseWasClicked())
  730. return;
  731. incDecDragged = true;
  732. mouseDragStartPos = e.position;
  733. }
  734. if (isAbsoluteDragMode (e.mods) || (maximum - minimum) / sliderRegionSize < interval)
  735. {
  736. dragMode = absoluteDrag;
  737. handleAbsoluteDrag (e);
  738. }
  739. else
  740. {
  741. dragMode = velocityDrag;
  742. handleVelocityDrag (e);
  743. }
  744. }
  745. valueWhenLastDragged = jlimit (minimum, maximum, valueWhenLastDragged);
  746. if (sliderBeingDragged == 0)
  747. {
  748. setValue (owner.snapValue (valueWhenLastDragged, dragMode),
  749. sendChangeOnlyOnRelease ? dontSendNotification : sendNotificationSync);
  750. }
  751. else if (sliderBeingDragged == 1)
  752. {
  753. setMinValue (owner.snapValue (valueWhenLastDragged, dragMode),
  754. sendChangeOnlyOnRelease ? dontSendNotification : sendNotificationAsync, true);
  755. if (e.mods.isShiftDown())
  756. setMaxValue (getMinValue() + minMaxDiff, dontSendNotification, true);
  757. else
  758. minMaxDiff = (double) valueMax.getValue() - (double) valueMin.getValue();
  759. }
  760. else if (sliderBeingDragged == 2)
  761. {
  762. setMaxValue (owner.snapValue (valueWhenLastDragged, dragMode),
  763. sendChangeOnlyOnRelease ? dontSendNotification : sendNotificationAsync, true);
  764. if (e.mods.isShiftDown())
  765. setMinValue (getMaxValue() - minMaxDiff, dontSendNotification, true);
  766. else
  767. minMaxDiff = (double) valueMax.getValue() - (double) valueMin.getValue();
  768. }
  769. mousePosWhenLastDragged = e.position;
  770. }
  771. }
  772. void mouseUp()
  773. {
  774. if (owner.isEnabled()
  775. && useDragEvents
  776. && (maximum > minimum)
  777. && (style != IncDecButtons || incDecDragged))
  778. {
  779. restoreMouseIfHidden();
  780. if (sendChangeOnlyOnRelease && valueOnMouseDown != (double) currentValue.getValue())
  781. triggerChangeMessage (sendNotificationAsync);
  782. currentDrag = nullptr;
  783. popupDisplay = nullptr;
  784. if (style == IncDecButtons)
  785. {
  786. incButton->setState (Button::buttonNormal);
  787. decButton->setState (Button::buttonNormal);
  788. }
  789. }
  790. else if (popupDisplay != nullptr)
  791. {
  792. popupDisplay->startTimer (2000);
  793. }
  794. currentDrag = nullptr;
  795. }
  796. bool canDoubleClickToValue() const
  797. {
  798. return doubleClickToValue
  799. && style != IncDecButtons
  800. && minimum <= doubleClickReturnValue
  801. && maximum >= doubleClickReturnValue;
  802. }
  803. void mouseDoubleClick()
  804. {
  805. if (canDoubleClickToValue())
  806. {
  807. DragInProgress drag (*this);
  808. setValue (doubleClickReturnValue, sendNotificationSync);
  809. }
  810. }
  811. bool mouseWheelMove (const MouseEvent& e, const MouseWheelDetails& wheel)
  812. {
  813. if (scrollWheelEnabled
  814. && style != TwoValueHorizontal
  815. && style != TwoValueVertical)
  816. {
  817. if (maximum > minimum && ! e.mods.isAnyMouseButtonDown())
  818. {
  819. if (valueBox != nullptr)
  820. valueBox->hideEditor (false);
  821. const double value = (double) currentValue.getValue();
  822. const double proportionDelta = (wheel.deltaX != 0 ? -wheel.deltaX : wheel.deltaY)
  823. * (wheel.isReversed ? -0.15f : 0.15f);
  824. const double currentPos = owner.valueToProportionOfLength (value);
  825. const double newValue = owner.proportionOfLengthToValue (jlimit (0.0, 1.0, currentPos + proportionDelta));
  826. double delta = (newValue != value) ? jmax (std::abs (newValue - value), interval) : 0;
  827. if (value > newValue)
  828. delta = -delta;
  829. DragInProgress drag (*this);
  830. setValue (owner.snapValue (value + delta, notDragging), sendNotificationSync);
  831. }
  832. return true;
  833. }
  834. return false;
  835. }
  836. void modifierKeysChanged (const ModifierKeys& modifiers)
  837. {
  838. if (style != IncDecButtons && style != Rotary && isAbsoluteDragMode (modifiers))
  839. restoreMouseIfHidden();
  840. }
  841. bool isAbsoluteDragMode (ModifierKeys mods) const
  842. {
  843. return isVelocityBased == (userKeyOverridesVelocity
  844. && mods.testFlags (ModifierKeys::ctrlAltCommandModifiers));
  845. }
  846. void restoreMouseIfHidden()
  847. {
  848. const Array<MouseInputSource>& mouseSources = Desktop::getInstance().getMouseSources();
  849. for (MouseInputSource* mi = mouseSources.begin(), * const e = mouseSources.end(); mi != e; ++mi)
  850. {
  851. if (mi->isUnboundedMouseMovementEnabled())
  852. {
  853. mi->enableUnboundedMouseMovement (false);
  854. const double pos = sliderBeingDragged == 2 ? getMaxValue()
  855. : (sliderBeingDragged == 1 ? getMinValue()
  856. : (double) currentValue.getValue());
  857. Point<float> mousePos;
  858. if (isRotary())
  859. {
  860. mousePos = mi->getLastMouseDownPosition();
  861. const float delta = (float) (pixelsForFullDragExtent * (owner.valueToProportionOfLength (valueOnMouseDown)
  862. - owner.valueToProportionOfLength (pos)));
  863. if (style == RotaryHorizontalDrag) mousePos += Point<float> (-delta, 0.0f);
  864. else if (style == RotaryVerticalDrag) mousePos += Point<float> (0.0f, delta);
  865. else mousePos += Point<float> (delta / -2.0f, delta / 2.0f);
  866. mousePos = owner.getScreenBounds().reduced (4).toFloat().getConstrainedPoint (mousePos);
  867. mouseDragStartPos = mousePosWhenLastDragged = owner.getLocalPoint (nullptr, mousePos);
  868. valueOnMouseDown = valueWhenLastDragged;
  869. }
  870. else
  871. {
  872. const float pixelPos = (float) getLinearSliderPos (pos);
  873. mousePos = owner.localPointToGlobal (Point<float> (isHorizontal() ? pixelPos : (owner.getWidth() / 2.0f),
  874. isVertical() ? pixelPos : (owner.getHeight() / 2.0f)));
  875. }
  876. mi->setScreenPosition (mousePos);
  877. }
  878. }
  879. }
  880. //==============================================================================
  881. void paint (Graphics& g, LookAndFeel& lf)
  882. {
  883. if (style != IncDecButtons)
  884. {
  885. if (isRotary())
  886. {
  887. const float sliderPos = (float) owner.valueToProportionOfLength (lastCurrentValue);
  888. jassert (sliderPos >= 0 && sliderPos <= 1.0f);
  889. lf.drawRotarySlider (g,
  890. sliderRect.getX(), sliderRect.getY(),
  891. sliderRect.getWidth(), sliderRect.getHeight(),
  892. sliderPos, rotaryStart, rotaryEnd, owner);
  893. }
  894. else
  895. {
  896. lf.drawLinearSlider (g,
  897. sliderRect.getX(), sliderRect.getY(),
  898. sliderRect.getWidth(), sliderRect.getHeight(),
  899. getLinearSliderPos (lastCurrentValue),
  900. getLinearSliderPos (lastValueMin),
  901. getLinearSliderPos (lastValueMax),
  902. style, owner);
  903. }
  904. if ((style == LinearBar || style == LinearBarVertical) && valueBox == nullptr)
  905. {
  906. g.setColour (owner.findColour (Slider::textBoxOutlineColourId));
  907. g.drawRect (0, 0, owner.getWidth(), owner.getHeight(), 1);
  908. }
  909. }
  910. }
  911. void resized (const Rectangle<int>& localBounds, LookAndFeel& lf)
  912. {
  913. int minXSpace = 0;
  914. int minYSpace = 0;
  915. if (textBoxPos == TextBoxLeft || textBoxPos == TextBoxRight)
  916. minXSpace = 30;
  917. else
  918. minYSpace = 15;
  919. const int tbw = jmax (0, jmin (textBoxWidth, localBounds.getWidth() - minXSpace));
  920. const int tbh = jmax (0, jmin (textBoxHeight, localBounds.getHeight() - minYSpace));
  921. if (style == LinearBar || style == LinearBarVertical)
  922. {
  923. if (valueBox != nullptr)
  924. valueBox->setBounds (localBounds);
  925. }
  926. else
  927. {
  928. if (textBoxPos == NoTextBox)
  929. {
  930. sliderRect = localBounds;
  931. }
  932. else if (textBoxPos == TextBoxLeft)
  933. {
  934. valueBox->setBounds (0, (localBounds.getHeight() - tbh) / 2, tbw, tbh);
  935. sliderRect.setBounds (tbw, 0, localBounds.getWidth() - tbw, localBounds.getHeight());
  936. }
  937. else if (textBoxPos == TextBoxRight)
  938. {
  939. valueBox->setBounds (localBounds.getWidth() - tbw, (localBounds.getHeight() - tbh) / 2, tbw, tbh);
  940. sliderRect.setBounds (0, 0, localBounds.getWidth() - tbw, localBounds.getHeight());
  941. }
  942. else if (textBoxPos == TextBoxAbove)
  943. {
  944. valueBox->setBounds ((localBounds.getWidth() - tbw) / 2, 0, tbw, tbh);
  945. sliderRect.setBounds (0, tbh, localBounds.getWidth(), localBounds.getHeight() - tbh);
  946. }
  947. else if (textBoxPos == TextBoxBelow)
  948. {
  949. valueBox->setBounds ((localBounds.getWidth() - tbw) / 2, localBounds.getHeight() - tbh, tbw, tbh);
  950. sliderRect.setBounds (0, 0, localBounds.getWidth(), localBounds.getHeight() - tbh);
  951. }
  952. }
  953. const int indent = lf.getSliderThumbRadius (owner);
  954. if (style == LinearBar)
  955. {
  956. const int barIndent = 1;
  957. sliderRegionStart = barIndent;
  958. sliderRegionSize = localBounds.getWidth() - barIndent * 2;
  959. sliderRect.setBounds (sliderRegionStart, barIndent,
  960. sliderRegionSize, localBounds.getHeight() - barIndent * 2);
  961. }
  962. else if (style == LinearBarVertical)
  963. {
  964. const int barIndent = 1;
  965. sliderRegionStart = barIndent;
  966. sliderRegionSize = localBounds.getHeight() - barIndent * 2;
  967. sliderRect.setBounds (barIndent, sliderRegionStart,
  968. localBounds.getWidth() - barIndent * 2, sliderRegionSize);
  969. }
  970. else if (isHorizontal())
  971. {
  972. sliderRegionStart = sliderRect.getX() + indent;
  973. sliderRegionSize = jmax (1, sliderRect.getWidth() - indent * 2);
  974. sliderRect.setBounds (sliderRegionStart, sliderRect.getY(),
  975. sliderRegionSize, sliderRect.getHeight());
  976. }
  977. else if (isVertical())
  978. {
  979. sliderRegionStart = sliderRect.getY() + indent;
  980. sliderRegionSize = jmax (1, sliderRect.getHeight() - indent * 2);
  981. sliderRect.setBounds (sliderRect.getX(), sliderRegionStart,
  982. sliderRect.getWidth(), sliderRegionSize);
  983. }
  984. else
  985. {
  986. sliderRegionStart = 0;
  987. sliderRegionSize = 100;
  988. }
  989. if (style == IncDecButtons)
  990. resizeIncDecButtons();
  991. }
  992. void resizeIncDecButtons()
  993. {
  994. Rectangle<int> buttonRect (sliderRect);
  995. if (textBoxPos == TextBoxLeft || textBoxPos == TextBoxRight)
  996. buttonRect.expand (-2, 0);
  997. else
  998. buttonRect.expand (0, -2);
  999. incDecButtonsSideBySide = buttonRect.getWidth() > buttonRect.getHeight();
  1000. if (incDecButtonsSideBySide)
  1001. {
  1002. decButton->setBounds (buttonRect.removeFromLeft (buttonRect.getWidth() / 2));
  1003. decButton->setConnectedEdges (Button::ConnectedOnRight);
  1004. incButton->setConnectedEdges (Button::ConnectedOnLeft);
  1005. }
  1006. else
  1007. {
  1008. decButton->setBounds (buttonRect.removeFromBottom (buttonRect.getHeight() / 2));
  1009. decButton->setConnectedEdges (Button::ConnectedOnTop);
  1010. incButton->setConnectedEdges (Button::ConnectedOnBottom);
  1011. }
  1012. incButton->setBounds (buttonRect);
  1013. }
  1014. //==============================================================================
  1015. Slider& owner;
  1016. SliderStyle style;
  1017. ListenerList <SliderListener> listeners;
  1018. Value currentValue, valueMin, valueMax;
  1019. double lastCurrentValue, lastValueMin, lastValueMax;
  1020. double minimum, maximum, interval, doubleClickReturnValue;
  1021. double valueWhenLastDragged, valueOnMouseDown, skewFactor, lastAngle;
  1022. double velocityModeSensitivity, velocityModeOffset, minMaxDiff;
  1023. int velocityModeThreshold;
  1024. float rotaryStart, rotaryEnd;
  1025. Point<float> mouseDragStartPos, mousePosWhenLastDragged;
  1026. int sliderRegionStart, sliderRegionSize;
  1027. int sliderBeingDragged;
  1028. int pixelsForFullDragExtent;
  1029. Rectangle<int> sliderRect;
  1030. ScopedPointer<DragInProgress> currentDrag;
  1031. TextEntryBoxPosition textBoxPos;
  1032. String textSuffix;
  1033. int numDecimalPlaces;
  1034. int textBoxWidth, textBoxHeight;
  1035. IncDecButtonMode incDecButtonMode;
  1036. bool editableText;
  1037. bool doubleClickToValue;
  1038. bool isVelocityBased;
  1039. bool userKeyOverridesVelocity;
  1040. bool rotaryStop;
  1041. bool incDecButtonsSideBySide;
  1042. bool sendChangeOnlyOnRelease;
  1043. bool popupDisplayEnabled;
  1044. bool menuEnabled;
  1045. bool useDragEvents;
  1046. bool incDecDragged;
  1047. bool scrollWheelEnabled;
  1048. bool snapsToMousePos;
  1049. ScopedPointer<Label> valueBox;
  1050. ScopedPointer<Button> incButton, decButton;
  1051. //==============================================================================
  1052. class PopupDisplayComponent : public BubbleComponent,
  1053. public Timer
  1054. {
  1055. public:
  1056. PopupDisplayComponent (Slider& s)
  1057. : owner (s),
  1058. font (s.getLookAndFeel().getSliderPopupFont (s))
  1059. {
  1060. setAlwaysOnTop (true);
  1061. setAllowedPlacement (owner.getLookAndFeel().getSliderPopupPlacement (s));
  1062. }
  1063. void paintContent (Graphics& g, int w, int h)
  1064. {
  1065. g.setFont (font);
  1066. g.setColour (owner.findColour (TooltipWindow::textColourId, true));
  1067. g.drawFittedText (text, Rectangle<int> (w, h), Justification::centred, 1);
  1068. }
  1069. void getContentSize (int& w, int& h)
  1070. {
  1071. w = font.getStringWidth (text) + 18;
  1072. h = (int) (font.getHeight() * 1.6f);
  1073. }
  1074. void updatePosition (const String& newText)
  1075. {
  1076. text = newText;
  1077. BubbleComponent::setPosition (&owner);
  1078. repaint();
  1079. }
  1080. void timerCallback() override
  1081. {
  1082. owner.pimpl->popupDisplay = nullptr;
  1083. }
  1084. private:
  1085. Slider& owner;
  1086. Font font;
  1087. String text;
  1088. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PopupDisplayComponent)
  1089. };
  1090. ScopedPointer<PopupDisplayComponent> popupDisplay;
  1091. Component* parentForPopupDisplay;
  1092. //==============================================================================
  1093. static double smallestAngleBetween (const double a1, const double a2) noexcept
  1094. {
  1095. return jmin (std::abs (a1 - a2),
  1096. std::abs (a1 + double_Pi * 2.0 - a2),
  1097. std::abs (a2 + double_Pi * 2.0 - a1));
  1098. }
  1099. };
  1100. //==============================================================================
  1101. Slider::Slider()
  1102. {
  1103. init (LinearHorizontal, TextBoxLeft);
  1104. }
  1105. Slider::Slider (const String& name) : Component (name)
  1106. {
  1107. init (LinearHorizontal, TextBoxLeft);
  1108. }
  1109. Slider::Slider (SliderStyle style, TextEntryBoxPosition textBoxPos)
  1110. {
  1111. init (style, textBoxPos);
  1112. }
  1113. void Slider::init (SliderStyle style, TextEntryBoxPosition textBoxPos)
  1114. {
  1115. setWantsKeyboardFocus (false);
  1116. setRepaintsOnMouseActivity (true);
  1117. pimpl = new Pimpl (*this, style, textBoxPos);
  1118. Slider::lookAndFeelChanged();
  1119. updateText();
  1120. pimpl->registerListeners();
  1121. }
  1122. Slider::~Slider() {}
  1123. //==============================================================================
  1124. void Slider::addListener (SliderListener* const listener) { pimpl->listeners.add (listener); }
  1125. void Slider::removeListener (SliderListener* const listener) { pimpl->listeners.remove (listener); }
  1126. //==============================================================================
  1127. Slider::SliderStyle Slider::getSliderStyle() const noexcept { return pimpl->style; }
  1128. void Slider::setSliderStyle (const SliderStyle newStyle) { pimpl->setSliderStyle (newStyle); }
  1129. void Slider::setRotaryParameters (const float startAngleRadians, const float endAngleRadians, const bool stopAtEnd)
  1130. {
  1131. pimpl->setRotaryParameters (startAngleRadians, endAngleRadians, stopAtEnd);
  1132. }
  1133. void Slider::setVelocityBasedMode (bool vb) { pimpl->isVelocityBased = vb; }
  1134. bool Slider::getVelocityBasedMode() const noexcept { return pimpl->isVelocityBased; }
  1135. bool Slider::getVelocityModeIsSwappable() const noexcept { return pimpl->userKeyOverridesVelocity; }
  1136. int Slider::getVelocityThreshold() const noexcept { return pimpl->velocityModeThreshold; }
  1137. double Slider::getVelocitySensitivity() const noexcept { return pimpl->velocityModeSensitivity; }
  1138. double Slider::getVelocityOffset() const noexcept { return pimpl->velocityModeOffset; }
  1139. void Slider::setVelocityModeParameters (const double sensitivity, const int threshold,
  1140. const double offset, const bool userCanPressKeyToSwapMode)
  1141. {
  1142. jassert (threshold >= 0);
  1143. jassert (sensitivity > 0);
  1144. jassert (offset >= 0);
  1145. pimpl->setVelocityModeParameters (sensitivity, threshold, offset, userCanPressKeyToSwapMode);
  1146. }
  1147. double Slider::getSkewFactor() const noexcept { return pimpl->skewFactor; }
  1148. void Slider::setSkewFactor (const double factor) { pimpl->skewFactor = factor; }
  1149. void Slider::setSkewFactorFromMidPoint (const double sliderValueToShowAtMidPoint)
  1150. {
  1151. pimpl->setSkewFactorFromMidPoint (sliderValueToShowAtMidPoint);
  1152. }
  1153. int Slider::getMouseDragSensitivity() const noexcept { return pimpl->pixelsForFullDragExtent; }
  1154. void Slider::setMouseDragSensitivity (const int distanceForFullScaleDrag)
  1155. {
  1156. jassert (distanceForFullScaleDrag > 0);
  1157. pimpl->pixelsForFullDragExtent = distanceForFullScaleDrag;
  1158. }
  1159. void Slider::setIncDecButtonsMode (const IncDecButtonMode mode) { pimpl->setIncDecButtonsMode (mode); }
  1160. Slider::TextEntryBoxPosition Slider::getTextBoxPosition() const noexcept { return pimpl->textBoxPos; }
  1161. int Slider::getTextBoxWidth() const noexcept { return pimpl->textBoxWidth; }
  1162. int Slider::getTextBoxHeight() const noexcept { return pimpl->textBoxHeight; }
  1163. void Slider::setTextBoxStyle (const TextEntryBoxPosition newPosition, const bool isReadOnly,
  1164. const int textEntryBoxWidth, const int textEntryBoxHeight)
  1165. {
  1166. pimpl->setTextBoxStyle (newPosition, isReadOnly, textEntryBoxWidth, textEntryBoxHeight);
  1167. }
  1168. bool Slider::isTextBoxEditable() const noexcept { return pimpl->editableText; }
  1169. void Slider::setTextBoxIsEditable (const bool shouldBeEditable) { pimpl->setTextBoxIsEditable (shouldBeEditable); }
  1170. void Slider::showTextBox() { pimpl->showTextBox(); }
  1171. void Slider::hideTextBox (const bool discardCurrentEditorContents) { pimpl->hideTextBox (discardCurrentEditorContents); }
  1172. void Slider::setChangeNotificationOnlyOnRelease (bool onlyNotifyOnRelease)
  1173. {
  1174. pimpl->sendChangeOnlyOnRelease = onlyNotifyOnRelease;
  1175. }
  1176. bool Slider::getSliderSnapsToMousePosition() const noexcept { return pimpl->snapsToMousePos; }
  1177. void Slider::setSliderSnapsToMousePosition (const bool shouldSnapToMouse) { pimpl->snapsToMousePos = shouldSnapToMouse; }
  1178. void Slider::setPopupDisplayEnabled (const bool enabled, Component* const parentComponentToUse)
  1179. {
  1180. pimpl->popupDisplayEnabled = enabled;
  1181. pimpl->parentForPopupDisplay = parentComponentToUse;
  1182. }
  1183. Component* Slider::getCurrentPopupDisplay() const noexcept { return pimpl->popupDisplay.get(); }
  1184. //==============================================================================
  1185. void Slider::colourChanged() { lookAndFeelChanged(); }
  1186. void Slider::lookAndFeelChanged() { pimpl->lookAndFeelChanged (getLookAndFeel()); }
  1187. void Slider::enablementChanged() { repaint(); }
  1188. //==============================================================================
  1189. double Slider::getMaximum() const noexcept { return pimpl->maximum; }
  1190. double Slider::getMinimum() const noexcept { return pimpl->minimum; }
  1191. double Slider::getInterval() const noexcept { return pimpl->interval; }
  1192. void Slider::setRange (double newMin, double newMax, double newInt)
  1193. {
  1194. pimpl->setRange (newMin, newMax, newInt);
  1195. }
  1196. Value& Slider::getValueObject() noexcept { return pimpl->currentValue; }
  1197. Value& Slider::getMinValueObject() noexcept { return pimpl->valueMin; }
  1198. Value& Slider::getMaxValueObject() noexcept { return pimpl->valueMax; }
  1199. double Slider::getValue() const { return pimpl->getValue(); }
  1200. void Slider::setValue (double newValue, const NotificationType notification)
  1201. {
  1202. pimpl->setValue (newValue, notification);
  1203. }
  1204. double Slider::getMinValue() const { return pimpl->getMinValue(); }
  1205. double Slider::getMaxValue() const { return pimpl->getMaxValue(); }
  1206. void Slider::setMinValue (double newValue, const NotificationType notification, bool allowNudgingOfOtherValues)
  1207. {
  1208. pimpl->setMinValue (newValue, notification, allowNudgingOfOtherValues);
  1209. }
  1210. void Slider::setMaxValue (double newValue, const NotificationType notification, bool allowNudgingOfOtherValues)
  1211. {
  1212. pimpl->setMaxValue (newValue, notification, allowNudgingOfOtherValues);
  1213. }
  1214. void Slider::setMinAndMaxValues (double newMinValue, double newMaxValue, const NotificationType notification)
  1215. {
  1216. pimpl->setMinAndMaxValues (newMinValue, newMaxValue, notification);
  1217. }
  1218. void Slider::setDoubleClickReturnValue (bool isDoubleClickEnabled, double valueToSetOnDoubleClick)
  1219. {
  1220. pimpl->doubleClickToValue = isDoubleClickEnabled;
  1221. pimpl->doubleClickReturnValue = valueToSetOnDoubleClick;
  1222. }
  1223. double Slider::getDoubleClickReturnValue (bool& isEnabledResult) const
  1224. {
  1225. isEnabledResult = pimpl->doubleClickToValue;
  1226. return pimpl->doubleClickReturnValue;
  1227. }
  1228. void Slider::updateText()
  1229. {
  1230. pimpl->updateText();
  1231. }
  1232. void Slider::setTextValueSuffix (const String& suffix)
  1233. {
  1234. pimpl->setTextValueSuffix (suffix);
  1235. }
  1236. String Slider::getTextValueSuffix() const
  1237. {
  1238. return pimpl->textSuffix;
  1239. }
  1240. String Slider::getTextFromValue (double v)
  1241. {
  1242. if (getNumDecimalPlacesToDisplay() > 0)
  1243. return String (v, getNumDecimalPlacesToDisplay()) + getTextValueSuffix();
  1244. return String (roundToInt (v)) + getTextValueSuffix();
  1245. }
  1246. double Slider::getValueFromText (const String& text)
  1247. {
  1248. String t (text.trimStart());
  1249. if (t.endsWith (getTextValueSuffix()))
  1250. t = t.substring (0, t.length() - getTextValueSuffix().length());
  1251. while (t.startsWithChar ('+'))
  1252. t = t.substring (1).trimStart();
  1253. return t.initialSectionContainingOnly ("0123456789.,-")
  1254. .getDoubleValue();
  1255. }
  1256. double Slider::proportionOfLengthToValue (double proportion)
  1257. {
  1258. const double skew = getSkewFactor();
  1259. if (skew != 1.0 && proportion > 0.0)
  1260. proportion = exp (log (proportion) / skew);
  1261. return getMinimum() + (getMaximum() - getMinimum()) * proportion;
  1262. }
  1263. double Slider::valueToProportionOfLength (double value)
  1264. {
  1265. const double n = (value - getMinimum()) / (getMaximum() - getMinimum());
  1266. const double skew = getSkewFactor();
  1267. return skew == 1.0 ? n : pow (n, skew);
  1268. }
  1269. double Slider::snapValue (double attemptedValue, DragMode)
  1270. {
  1271. return attemptedValue;
  1272. }
  1273. int Slider::getNumDecimalPlacesToDisplay() const noexcept { return pimpl->numDecimalPlaces; }
  1274. //==============================================================================
  1275. int Slider::getThumbBeingDragged() const noexcept { return pimpl->sliderBeingDragged; }
  1276. void Slider::startedDragging() {}
  1277. void Slider::stoppedDragging() {}
  1278. void Slider::valueChanged() {}
  1279. //==============================================================================
  1280. void Slider::setPopupMenuEnabled (const bool menuEnabled) { pimpl->menuEnabled = menuEnabled; }
  1281. void Slider::setScrollWheelEnabled (const bool enabled) { pimpl->scrollWheelEnabled = enabled; }
  1282. bool Slider::isHorizontal() const noexcept { return pimpl->isHorizontal(); }
  1283. bool Slider::isVertical() const noexcept { return pimpl->isVertical(); }
  1284. float Slider::getPositionOfValue (const double value) { return pimpl->getPositionOfValue (value); }
  1285. //==============================================================================
  1286. void Slider::paint (Graphics& g) { pimpl->paint (g, getLookAndFeel()); }
  1287. void Slider::resized() { pimpl->resized (getLocalBounds(), getLookAndFeel()); }
  1288. void Slider::focusOfChildComponentChanged (FocusChangeType) { repaint(); }
  1289. void Slider::mouseDown (const MouseEvent& e) { pimpl->mouseDown (e); }
  1290. void Slider::mouseUp (const MouseEvent&) { pimpl->mouseUp(); }
  1291. void Slider::modifierKeysChanged (const ModifierKeys& modifiers)
  1292. {
  1293. if (isEnabled())
  1294. pimpl->modifierKeysChanged (modifiers);
  1295. }
  1296. void Slider::mouseDrag (const MouseEvent& e)
  1297. {
  1298. if (isEnabled())
  1299. pimpl->mouseDrag (e);
  1300. }
  1301. void Slider::mouseDoubleClick (const MouseEvent&)
  1302. {
  1303. if (isEnabled())
  1304. pimpl->mouseDoubleClick();
  1305. }
  1306. void Slider::mouseWheelMove (const MouseEvent& e, const MouseWheelDetails& wheel)
  1307. {
  1308. if (! (isEnabled() && pimpl->mouseWheelMove (e, wheel)))
  1309. Component::mouseWheelMove (e, wheel);
  1310. }