Krotos Modules 3
Loading...
Searching...
No Matches
XYButton.cpp
Go to the documentation of this file.
1#include "XYButton.h"
2
3namespace krotos
4{
5 const std::string XYButton::analyticsEventMouseDown{"analyticsEventMouseDown"};
6
7 XYButton::XYButton(String xLabel, String /*yLabel*/) : Button(xLabel)
8 {
9 m_background = Drawable::createFromImageData(KrotosBinaryData::XYPad_Background_png,
10 KrotosBinaryData::XYPad_Background_pngSize);
11 }
12
14 {
16 {
17 m_background->drawWithin(g, m_bounds.toFloat(), RectanglePlacement::stretchToFit, 1.0f);
18
20 {
21 m_twoZoneMask->drawWithin(g, m_maskBounds.toFloat(), RectanglePlacement::stretchToFit, 0.1f);
22 }
23 else
24 {
25 m_twoZoneMask->drawWithin(g, m_maskBounds.toFloat(), RectanglePlacement::stretchToFit, 0.2f);
26 }
27 }
28
29 // Flip the y axis to convert GUI coords to screen coords
30 auto flippedPuckPosition = Point<float>(m_puckPosition.x, 1.f - m_puckPosition.y);
31
32 // Scale movement to the size of the boundary
33 m_scaledPuckPosition = flippedPuckPosition *
34 Point<float>(m_puckBoundary.toFloat().getWidth(), m_puckBoundary.toFloat().getHeight());
35
36 // Center the movement with respect to the overall bounds
38 Point<float>(static_cast<float>(m_layout.puckRadius), static_cast<float>(m_layout.puckRadius));
39
40 if (m_paintBackground) // draw classic puck
41 {
42 // Ellipse lines are measured to the center of the line, so make the
43 // draw bounds smaller by line thickness so we get the diameter we
44 // need
45 const int puckDrawDiameter = m_layout.puckDiameter - m_layout.puckLineWidth;
46
47 // Create an enclosing rectangle for the puck
48 m_puckBounds = Rectangle<float>(0.f, 0.f, static_cast<float>(puckDrawDiameter),
49 static_cast<float>(puckDrawDiameter))
50 .withCentre(m_scaledPuckPosition);
51
52 // Draw the puck
54 {
55 g.setColour(m_layout.colourPuckMouseOver);
56 g.fillEllipse(m_puckBounds); // Fill in the center
57 }
58 else if (m_mouseIsDown)
59 {
60 g.setColour(m_layout.colourPuckMouseDown);
61 }
62 else
63 {
64 g.setColour(m_layout.colourPuckDefault);
65 }
66
67 // fillEllipse is smaller in diameter than drawEllipse by line thickness, and is not anti-aliased
68 // so will filled ellipse we still draw in the drawEllipse below to bring diameter back to size and
69 // smoothness
70 g.drawEllipse(m_puckBounds, static_cast<float>(m_layout.puckLineWidth)); // Draw the outline
71 }
72 else // draw crosshair
73 {
74 float minOpacity = 0.0f;
75 float maxOpacity = m_mouseOverPuck || m_mouseIsDown ? 1.0f : 0.5f;
76
77 Colour crosshairColor = Colours::white;
78
79 g.setColour(crosshairColor);
80
81 // Horizontal line (top)
84 m_layout.crosshairLineWidth, minOpacity, maxOpacity);
85
86 // Horizontal line (bottom)
89 m_layout.crosshairLineWidth, minOpacity, maxOpacity);
90
91 // Vertical line (left)
94 m_layout.crosshairLineWidth, minOpacity, maxOpacity);
95
96 // Vertical line (right)
99 m_layout.crosshairLineWidth, minOpacity, maxOpacity);
100
101 // Draw a hollow square box with rounded corners to represent the crosshair gap when the mouse is down
102 if (m_mouseIsDown)
103 {
104 float squareSize = m_layout.crosshairGap; // set the square size to be the same as the gap
105 float xStart = m_scaledPuckPosition.x - squareSize / 2;
106 float yStart = m_scaledPuckPosition.y - squareSize / 2;
107 float cornerRadius = 1.0f;
108
109 // create a path object to draw the rounded rectangle
110 Path roundedRectPath;
111
112 // Define the four corners of the rectangle with rounded corners
113 roundedRectPath.addRoundedRectangle(xStart, yStart, squareSize, squareSize, cornerRadius);
114
115 // Use the strokePath method to draw the path with the specified line width
116 g.strokePath(roundedRectPath, PathStrokeType(m_layout.crosshairLineWidth));
117 }
118 }
119 }
120
121 void XYButton::drawGradientLine(Graphics& g, float startX, float startY, float endX, float endY, float lineWidth,
122 float minOpacity, float maxOpacity)
123 {
124 float steps = std::max(std::abs(endX - startX), std::abs(endY - startY));
125 float xStep = (endX - startX) / steps;
126 float yStep = (endY - startY) / steps;
127
128 float opacityStep = (maxOpacity - minOpacity) / steps;
129
130 for (int i = 0; i < steps; ++i)
131 {
132 float xStart = startX + i * xStep;
133 float yStart = startY + i * yStep;
134 float xEnd = startX + (i + 1) * xStep;
135 float yEnd = startY + (i + 1) * yStep;
136
137 float opacity = maxOpacity - i * opacityStep;
138 g.setOpacity(opacity);
139
140 g.drawLine(xStart, yStart, xEnd, yEnd, lineWidth);
141 }
142 // reset opacity
143 g.setOpacity(1.0f);
144 }
145
147 {
148 m_bounds = getLocalBounds();
150
151 if (m_maskStreched)
152 {
153 m_maskBounds = m_bounds.expanded(m_bounds.getWidth() / 2);
154 }
155 else
156 {
157 m_wholeBounds = getLocalBounds();
158 m_wholeBounds.removeFromBottom(15);
159 m_wholeBounds.removeFromLeft(8);
160 m_wholeBounds.removeFromRight(8);
162 }
163 }
164
166 {
167 if (x == 1.0f)
168 m_toggleValue = true;
169 else if (x == 0.0f)
170 m_toggleValue = false;
171 }
172
174
175 void XYButton::setPuckPosition(Point<int> position)
176 {
177 auto clampedOffsetPos =
178 m_puckBoundary.getConstrainedPoint(position).toFloat(); // Clamp the raw position within m_puckBoundary
179 clampedOffsetPos -= Point<int>(m_layout.puckRadius, m_layout.puckRadius)
180 .toFloat(); // Offset it by puck radius to center puck on mouse pointer
181 clampedOffsetPos /= Point<int>(m_puckBoundary.getWidth(), m_puckBoundary.getHeight())
182 .toFloat(); // Convert from pixel values to normalised 0.0f -> 1.0f
183 clampedOffsetPos.setY(1.0f - clampedOffsetPos.getY()); // Flip the Y axis to match screen coords to GUI coords
184 setNormalisedPuckPosition(clampedOffsetPos);
185 }
186
188 {
189 m_puckPosition = pos;
190 if (puckPositionChanged != nullptr)
192 repaint();
193 }
194
195 void XYButton::mouseMove(const MouseEvent& event)
196 {
197 // check if the mouse is over the circular puck
198 float puckRadius = m_layout.puckRadius;
199 Point<float> puckCenter = m_scaledPuckPosition;
200
201 float distanceToPuck = event.position.getDistanceFrom(puckCenter);
202
203 bool isMouseOverPuck = distanceToPuck <= puckRadius;
204
205 // check if the mouse is over the crosshair lines
206 // convert mouse position to scaled coordinates
207 Point<float> mousePosition = event.position;
208 mousePosition =
209 mousePosition * Point<float>(m_puckBoundary.toFloat().getWidth(), m_puckBoundary.toFloat().getHeight());
210 mousePosition += Point<float>(static_cast<float>(m_layout.puckRadius), static_cast<float>(m_layout.puckRadius));
211
212 // calculate the boundaries of the crosshair lines
213 Rectangle<float> horizontalTopLine(m_scaledPuckPosition.x - m_layout.crosshairLength / 2,
216
217 Rectangle<float> horizontalBottomLine(m_scaledPuckPosition.x - m_layout.crosshairLength / 2,
220
221 Rectangle<float> verticalLeftLine(m_scaledPuckPosition.x - m_layout.crosshairLineWidth / 2,
224
225 Rectangle<float> verticalRightLine(m_scaledPuckPosition.x - m_layout.crosshairLineWidth / 2,
228
229 // check if the mouse is over any of the crosshair lines
230 bool isMouseOverCrosshair =
231 horizontalTopLine.contains(mousePosition) || horizontalBottomLine.contains(mousePosition) ||
232 verticalLeftLine.contains(mousePosition) || verticalRightLine.contains(mousePosition);
233
234 // update the mouseOverPuck state based on both conditions
235 m_mouseOverPuck = isMouseOverPuck || isMouseOverCrosshair;
236
237 repaint();
238 }
239
240 void XYButton::mouseDrag(const MouseEvent& event)
241 {
242 setPuckPosition(event.getPosition());
243 Button::mouseDrag(event);
244 }
245
246 void XYButton::mouseDown(const MouseEvent& event)
247 {
248 // Broadcasting to action listeners that an analytics event took place
249 sendActionMessage(analyticsEventMouseDown);
250
251 m_mouseIsDown = true;
252
253 setMouseCursor(MouseCursor::NoCursor);
254
255 setPuckPosition(event.getPosition());
256 Button::mouseDown(event);
257
258 if (dragStarted != nullptr)
259 dragStarted();
260 }
261
262 void XYButton::mouseUp(const MouseEvent& event)
263 {
264 m_mouseIsDown = false;
265
266 setMouseCursor(MouseCursor::NormalCursor);
267
268 Button::mouseUp(event);
269
270 if (dragEnded != nullptr)
271 dragEnded();
272 }
273
274 void XYButton::createMask(const void* data, const size_t numBytes)
275 {
276 m_twoZoneMask = Drawable::createFromImageData(data, numBytes);
277 }
278
279 XYButtonParameterAttachment::XYButtonParameterAttachment(RangedAudioParameter& xParam, RangedAudioParameter& yParam,
280 RangedAudioParameter& clickParam,
281 RangedAudioParameter& toggleParam, XYButton& xy,
282 UndoManager* /*um*/)
283 : m_xyButton(xy), m_xAttachment(xParam, [this](float x) { setXValue(x); }),
284 m_yAttachment(yParam, [this](float y) { setYValue(y); }), m_clickAttachment(clickParam, {}),
285 m_toggleAttachment(toggleParam, [this](float x) { setToggleValue(x); })
286 {
287 sendInitialUpdate();
288
289 m_xyButton.dragStarted = [this] {
290 m_xAttachment.beginGesture();
291 m_yAttachment.beginGesture();
292 if (m_xyButton.getToggleValue())
293 {
294 m_clickAttachment.setValueAsCompleteGesture(1.0f);
295 }
296 };
297
298 m_xyButton.dragEnded = [this] {
299 m_xAttachment.endGesture();
300 m_yAttachment.endGesture();
301 if (m_xyButton.getToggleValue())
302 {
303 m_clickAttachment.setValueAsCompleteGesture(0.0f);
304 }
305 };
306
307 m_xyButton.puckPositionChanged = [this](Point<float> p) {
308 if (!m_ignoreCallbacks)
309 {
310 m_xAttachment.setValueAsPartOfGesture(p.getX());
311 m_yAttachment.setValueAsPartOfGesture(p.getY());
312 }
313 };
314 }
315
317
319 {
320 m_xAttachment.sendInitialUpdate();
321 m_yAttachment.sendInitialUpdate();
322 m_toggleAttachment.sendInitialUpdate();
323 }
324
326 {
327 const ScopedValueSetter<bool> svs(m_ignoreCallbacks, true);
328
329 auto pos = m_xyButton.getPuckPosition();
330 pos.setX(x);
331
333 }
334
336 {
337 const ScopedValueSetter<bool> svs(m_ignoreCallbacks, true);
338
339 auto pos = m_xyButton.getPuckPosition();
340 pos.setY(y);
341
343 }
344
346} // namespace krotos
An XY Button UI component with a puck following the mouse position.
Definition XYButton.h:15
Point< float > m_scaledPuckPosition
Definition XYButton.h:107
void drawGradientLine(Graphics &g, float startX, float startY, float endX, float endY, float lineWidth, float minOpacity, float maxOpacity)
Definition XYButton.cpp:121
void mouseDrag(const MouseEvent &) override
Definition XYButton.cpp:240
void setToggleValue(float x)
Definition XYButton.cpp:165
bool m_toggleValue
Definition XYButton.h:120
void setPuckPosition(Point< int > pos)
Definition XYButton.cpp:175
XYButton(String xLabel="X", String yLabel="Y")
Definition XYButton.cpp:7
bool m_maskStreched
Definition XYButton.h:101
Rectangle< float > m_puckBounds
Definition XYButton.h:113
Rectangle< int > m_bounds
Definition XYButton.h:112
std::function< void(Point< float >)> puckPositionChanged
Definition XYButton.h:48
static const std::string analyticsEventMouseDown
Definition XYButton.h:21
std::function< void()> dragStarted
Definition XYButton.h:53
void mouseUp(const MouseEvent &) override
Definition XYButton.cpp:262
Rectangle< int > m_puckBoundary
Definition XYButton.h:114
void mouseDown(const MouseEvent &) override
Definition XYButton.cpp:246
Rectangle< int > m_maskBounds
Definition XYButton.h:115
Point< float > m_puckPosition
Definition XYButton.h:106
bool m_mouseOverPuck
Definition XYButton.h:110
bool getToggleValue()
Definition XYButton.cpp:173
struct krotos::XYButton::Layout m_layout
std::function< void()> dragEnded
Definition XYButton.h:58
void createMask(const void *data, const size_t numBytes)
Definition XYButton.cpp:274
void setNormalisedPuckPosition(Point< float > pos)
Definition XYButton.cpp:187
void paintOverChildren(Graphics &g) override
Definition XYButton.cpp:13
bool m_mouseIsDown
Definition XYButton.h:109
std::unique_ptr< Drawable > m_background
Definition XYButton.h:103
void mouseMove(const MouseEvent &) override
Definition XYButton.cpp:195
void resized() override
Definition XYButton.cpp:146
std::unique_ptr< Drawable > m_twoZoneMask
Definition XYButton.h:104
Point< float > getPuckPosition()
Definition XYButton.h:42
Rectangle< int > m_wholeBounds
Definition XYButton.h:116
bool m_paintBackground
Definition XYButton.h:118
void sendInitialUpdate()
Definition XYButton.cpp:318
ParameterAttachment m_xAttachment
Definition XYButton.h:159
~XYButtonParameterAttachment()
Definition XYButton.cpp:316
void setYValue(float yValue)
Definition XYButton.cpp:335
bool m_ignoreCallbacks
Definition XYButton.h:161
XYButton & m_xyButton
Definition XYButton.h:158
void setXValue(float xValue)
Definition XYButton.cpp:325
XYButtonParameterAttachment(RangedAudioParameter &xParam, RangedAudioParameter &yParam, RangedAudioParameter &clickParam, RangedAudioParameter &toggleParam, XYButton &slider, UndoManager *undoManager=nullptr)
Definition XYButton.cpp:279
ParameterAttachment m_toggleAttachment
Definition XYButton.h:159
ParameterAttachment m_yAttachment
Definition XYButton.h:159
void setToggleValue(float x)
Definition XYButton.cpp:345
Definition AirAbsorptionFilter.cpp:2
const Colour colourPuckDefault
Definition XYButton.h:86
const int crosshairLineWidth
Definition XYButton.h:80
const int puckDiameter
Definition XYButton.h:81
const int puckLineWidth
Definition XYButton.h:82
const float crosshairLength
Definition XYButton.h:78
const Colour colourPuckMouseDown
Definition XYButton.h:85
const float crosshairGap
Definition XYButton.h:79
const Colour colourPuckMouseOver
Definition XYButton.h:84
const int puckRadius
Definition XYButton.h:83