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

1761 lines
64KB

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