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.

1629 lines
58KB

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