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