The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
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.

1650 lines
59KB

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