juce_Slider.cpp 61 KB

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