-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTransportScrubber.cpp
More file actions
148 lines (129 loc) · 5.34 KB
/
Copy pathTransportScrubber.cpp
File metadata and controls
148 lines (129 loc) · 5.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
#include "TransportScrubber.h"
#include "ModernLookAndFeel.h"
#include <cmath>
namespace sp
{
TransportScrubber::TransportScrubber()
{
setMouseCursor (juce::MouseCursor::IBeamCursor);
}
void TransportScrubber::setDurationSeconds (double s)
{
const double clamped = juce::jmax (0.0, s);
if (duration != clamped)
{
duration = clamped;
if (position > duration) position = duration;
repaint();
}
}
void TransportScrubber::setPositionSeconds (double s)
{
const double clamped = juce::jlimit (0.0, duration, s);
if (position != clamped)
{
position = clamped;
repaint();
}
}
juce::Rectangle<float> TransportScrubber::railBounds() const
{
// Track sits centred vertically with a small inset so the thumb can extend
// past the rail without clipping. Time labels render below the rail.
const auto r = getLocalBounds().toFloat();
const float pad = 6.0f;
const float labelArea = 14.0f;
return juce::Rectangle<float> (r.getX() + pad,
r.getY() + (r.getHeight() - labelArea) * 0.5f - 4.0f,
r.getWidth() - pad * 2.0f,
8.0f);
}
double TransportScrubber::xToSeconds (float x) const
{
if (duration <= 0.0) return 0.0;
const auto rail = railBounds();
const float t = juce::jlimit (0.0f, 1.0f, (x - rail.getX()) / juce::jmax (1.0f, rail.getWidth()));
return t * duration;
}
float TransportScrubber::secondsToX (double s) const
{
const auto rail = railBounds();
if (duration <= 0.0) return rail.getX();
const double t = juce::jlimit (0.0, 1.0, s / duration);
return rail.getX() + (float) (t * rail.getWidth());
}
void TransportScrubber::seekTo (double seconds)
{
const double clamped = juce::jlimit (0.0, duration, seconds);
setPositionSeconds (clamped);
if (onSeek) onSeek (clamped);
}
static juce::String formatTime (double s)
{
if (s < 0.0 || ! std::isfinite (s)) return "--:--";
const int total = (int) std::floor (s);
const int mm = total / 60;
const int ss = total % 60;
return juce::String::formatted ("%d:%02d", mm, ss);
}
void TransportScrubber::paint (juce::Graphics& g)
{
g.fillAll (juce::Colour (ModernLookAndFeel::colSurface));
const auto rail = railBounds();
const float radius = rail.getHeight() * 0.5f;
const bool enabled = duration > 0.0;
// Rail
g.setColour (juce::Colour (ModernLookAndFeel::colInputBg));
g.fillRoundedRectangle (rail, radius);
g.setColour (juce::Colour (ModernLookAndFeel::colBorder));
g.drawRoundedRectangle (rail.reduced (0.5f), radius, 1.0f);
if (! enabled)
{
g.setColour (juce::Colour (ModernLookAndFeel::colTextMuted));
g.setFont (juce::Font (juce::FontOptions (11.0f)));
g.drawText ("(no instrumental loaded)", getLocalBounds(),
juce::Justification::centred);
return;
}
// Filled portion
const float playheadX = secondsToX (position);
g.setColour (juce::Colour (ModernLookAndFeel::colAccent));
g.fillRoundedRectangle (rail.withRight (playheadX), radius);
// Time labels (left = current, right = duration)
g.setColour (juce::Colour (ModernLookAndFeel::colTextSecondary));
g.setFont (juce::Font (juce::FontOptions (11.0f)));
const auto labelRow = juce::Rectangle<int> ((int) rail.getX(),
(int) rail.getBottom() + 2,
(int) rail.getWidth(), 12);
g.drawText (formatTime (position), labelRow, juce::Justification::topLeft);
g.drawText (formatTime (duration), labelRow, juce::Justification::topRight);
// Thumb (drawn last so it overlays everything).
const float thumbR = 7.0f;
const float thumbY = rail.getCentreY();
g.setColour (juce::Colour (ModernLookAndFeel::colTextPrimary));
g.fillEllipse (playheadX - thumbR, thumbY - thumbR, thumbR * 2.0f, thumbR * 2.0f);
g.setColour (juce::Colour (ModernLookAndFeel::colAccent));
g.drawEllipse (playheadX - thumbR, thumbY - thumbR, thumbR * 2.0f, thumbR * 2.0f, 1.5f);
}
void TransportScrubber::mouseDown (const juce::MouseEvent& e)
{
if (duration <= 0.0) return;
seekTo (xToSeconds ((float) e.x));
}
void TransportScrubber::mouseDrag (const juce::MouseEvent& e)
{
if (duration <= 0.0) return;
seekTo (xToSeconds ((float) e.x));
}
void TransportScrubber::mouseWheelMove (const juce::MouseEvent& e,
const juce::MouseWheelDetails& wheel)
{
if (duration <= 0.0) return;
// Use vertical wheel delta (or horizontal if a horizontal wheel is present).
const float dy = wheel.deltaX != 0.0f ? wheel.deltaX : wheel.deltaY;
if (dy == 0.0f) return;
// Shift = coarser (5 s/notch); plain = 1 s/notch.
const double step = e.mods.isShiftDown() ? 5.0 : 1.0;
seekTo (position - dy * step);
}
}