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.

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