Vital
Loading...
Searching...
No Matches
modulation_button.cpp
Go to the documentation of this file.
1#include "modulation_button.h"
2
4#include "modulation_matrix.h"
6#include "fonts.h"
7#include "skin.h"
8
9ModulationButton::ModulationButton(String name) : PlainShapeComponent(std::move(name)), parent_(nullptr),
10 mouse_state_(kNone), selected_(false), connect_right_(false),
11 draw_border_(false), active_modulation_(false), font_size_(12.0f),
12 show_drag_drop_(false), drag_drop_alpha_(0.0f) {
13 setWantsKeyboardFocus(true);
14 Path shape = Paths::dragDropArrows();
15 shape.addLineSegment(Line<float>(-50.0f, -50.0f, -50.0f, -50.0f), 0.2f);
17 setComponent(&drag_drop_area_);
18 setActive(false);
19 setUseAlpha(true);
20 setInterceptsMouseClicks(true, false);
21 addAndMakeVisible(drag_drop_area_);
22 drag_drop_area_.setInterceptsMouseClicks(false, false);
23 setColor(Colours::transparentWhite);
24}
25
27 if (parent_)
28 parent_->getSynth()->forceShowModulation(getName().toStdString(), false);
29}
30
32 if (parent_)
33 return parent_->getSynth()->isSourceConnected(getName().toStdString());
34 return false;
35}
36
37Rectangle<int> ModulationButton::getModulationAmountBounds(int index, int total) {
38 int columns = kModulationKnobColumns;
39
40 int row = index / columns;
41 int column = index % columns;
42
43 Rectangle<int> all_bounds = getModulationAreaBounds();
44 int x = all_bounds.getX() + (all_bounds.getWidth() * column) / columns;
45 int right = all_bounds.getX() + (all_bounds.getWidth() * (column + 1)) / columns;
46 int width = right - x;
47 int y = all_bounds.getY() + all_bounds.getHeight() - width * (row + 1);
48 return Rectangle<int>(x, y, width, width);
49}
50
52 static constexpr int kMinMeterWidth = 4;
53
54 int width = getWidth();
55 int meter_width = std::max<int>(kMinMeterWidth, std::round(width * kMeterAreaRatio / 2.0f) * 2);
56 int meter_height = getHeight() - 2;
57 return Rectangle<int>(1, 1, meter_width, meter_height);
58}
59
61 static constexpr int kMaxWidthHeightRatio = 3;
62
63 SynthSection* parent = findParentComponentOfClass<SynthSection>();
64 int widget_margin = 0;
65 if (parent)
66 widget_margin = parent->findValue(Skin::kWidgetMargin);
67
68 int width = getWidth() - getMeterBounds().getRight();
69 int height = getHeight();
70
71 int widget_width = width - 2 * widget_margin;
72 int knob_width = widget_width / kModulationKnobColumns;
73 widget_width = knob_width * kModulationKnobColumns;
74 int widget_x = getMeterBounds().getRight() + (width - widget_width) / 2;
75 int min_y = kFontAreaHeightRatio * width;
76 int max_widget_height = ceilf(widget_width * 2.0f / 3.0f);
77 int widget_y = std::max(min_y, height - widget_margin - max_widget_height);
78 int widget_height = height - widget_y - widget_margin;
79 int center_y = widget_y + widget_height / 2;
80 widget_height = std::max(widget_height, (widget_width + kMaxWidthHeightRatio - 1) / kMaxWidthHeightRatio);
81 widget_y = center_y - widget_height / 2;
82 return Rectangle<int>(widget_x, widget_y, widget_width, widget_height);
83}
84
86 static constexpr float kShadowArea = 0.04f;
87
88 if (getWidth() == 0 || getHeight() == 0)
89 return;
90
91 // Color based on selection
92 if (selected_)
93 g.setColour(findColour(Skin::kModulationButtonSelected, true));
94 else
95 g.setColour(findColour(Skin::kModulationButtonUnselected, true));
96
97 SynthSection* parent = findParentComponentOfClass<SynthSection>();
98 int rounding_amount = 0;
99 if (parent)
100 rounding_amount = parent->findValue(Skin::kBodyRounding);
101
102 Rectangle<float> meter_bounds = getMeterBounds().toFloat();
103 int width = getWidth();
104 int adjusted_width = connect_right_ ? width * 2 : width;
105 Rectangle<float> bounds(0, 0, adjusted_width, getHeight());
106 g.fillRoundedRectangle(bounds, rounding_amount);
107
108 g.setColour(findColour(Skin::kWidgetBackground, true));
109 g.fillRoundedRectangle(meter_bounds, meter_bounds.getWidth() / 2.0f);
110 float meter_width = meter_bounds.getWidth();
111 g.fillRect(meter_bounds.getX() + meter_width / 2.0f, meter_bounds.getY(), meter_width / 2, meter_bounds.getHeight());
112
113 if (draw_border_) {
114 g.setColour(findColour(Skin::kBorder, true));
115 g.drawRoundedRectangle(bounds.reduced(0.5f), rounding_amount, 1.0f);
116 }
117
118 int height = getHeight();
119 g.setColour(findColour(Skin::kBodyText, true));
120 g.setFont(Fonts::instance()->proportional_regular().withPointHeight(font_size_));
121 String text = text_override_;
122 if (text.isEmpty())
124
125 int font_area_height = kFontAreaHeightRatio * width;
126 g.drawText(text, meter_bounds.getRight(), 0, width - meter_bounds.getRight(),
127 font_area_height, Justification::centred);
128
129 if (connect_right_ && !selected_) {
130 int shadow_width = width * kShadowArea;
131 Colour shadow_color = findColour(Skin::kShadow, true);
132 ColourGradient gradient(shadow_color, width, 0, shadow_color.withAlpha(0.0f), width - shadow_width, 0, false);
133 g.setGradientFill(gradient);
134 g.fillRect(width - shadow_width, 0, shadow_width, height);
135 }
136}
137
139 if (parent_ == nullptr) {
140 parent_ = findParentComponentOfClass<SynthGuiInterface>();
142 }
143}
144
146 static constexpr float kBorder = 0.2f;
147
149 Rectangle<float> meter_bounds = getMeterBounds().toFloat();
150 int left = meter_bounds.getRight();
151 int width = getWidth() - left;
152 int font_area_height = kFontAreaHeightRatio * width;
153 int top = font_area_height - (font_area_height - font_size_) * 0.5f;
154 int height = getHeight() - top;
155
156 float size_mult = 1.0f - 2.0f * kBorder;
157 drag_drop_area_.setBounds(left + width * kBorder, top + height * kBorder,
158 width * size_mult, height * size_mult);
159}
160
161void ModulationButton::render(OpenGlWrapper& open_gl, bool animate) {
162 static constexpr float kDeltaAlpha = 0.15f;
163
164 float target = 0.0f;
165 if (show_drag_drop_) {
166 target = 1.0f;
167 if (mouse_state_ == kMouseDown || mouse_state_ == kMouseDragging)
168 target = 2.0f;
169 }
170
171 bool increase = drag_drop_alpha_ < target;
172 if (increase)
173 drag_drop_alpha_ = std::min(drag_drop_alpha_ + kDeltaAlpha, target);
174 else
175 drag_drop_alpha_ = std::max(drag_drop_alpha_ - kDeltaAlpha, target);
176
177 if (drag_drop_alpha_ <= 0.0f) {
178 drag_drop_alpha_ = 0.0f;
179 setActive(false);
180 }
181
182 setColor(drag_drop_color_.withMultipliedAlpha(drag_drop_alpha_));
183 PlainShapeComponent::render(open_gl, animate);
184}
185
186void ModulationButton::mouseDown(const MouseEvent& e) {
187 if (e.mods.isPopupMenu()) {
188 if (parent_ == nullptr)
189 return;
190
191 std::vector<vital::ModulationConnection*> connections =
192 parent_->getSynth()->getSourceConnections(getName().toStdString());
193
194 if (connections.empty())
195 return;
196
197 mouse_state_ = kNone;
198
199 PopupItems options;
200 std::string disconnect = "Disconnect from ";
201 for (int i = 0; i < connections.size(); ++i) {
202 std::string destination = vital::Parameters::getDisplayName(connections[i]->destination_name);
203 options.addItem(kModulationList + i, disconnect + destination);
204 }
205
206 if (connections.size() > 1)
207 options.addItem(kDisconnect, "Disconnect all");
208
209 SynthSection* parent = findParentComponentOfClass<SynthSection>();
210 parent->showPopupSelector(this, e.getPosition(), options, [=](int selection) { disconnectIndex(selection); });
211 }
212 else {
214 mouse_state_ = kMouseDown;
215
216 for (Listener* listener : listeners_)
217 listener->modulationSelected(this);
218 }
219}
220
221void ModulationButton::mouseDrag(const MouseEvent& e) {
222 if (e.mods.isRightButtonDown())
223 return;
224
225 if (!getLocalBounds().contains(e.getPosition()) && mouse_state_ != kDraggingOut) {
226 for (Listener* listener : listeners_)
227 listener->startModulationMap(this, e);
228 mouse_state_ = kDraggingOut;
229 setMouseCursor(MouseCursor::DraggingHandCursor);
230 }
231
232 if (mouse_state_ == kDraggingOut) {
233 for (Listener* listener : listeners_)
234 listener->modulationDragged(e);
235 }
236 else if (mouse_state_ != kMouseDragging)
237 mouse_state_ = kMouseDragging;
238}
239
240void ModulationButton::mouseUp(const MouseEvent& e) {
241 if (!e.mods.isRightButtonDown() && mouse_state_ == kDraggingOut) {
242 for (Listener* listener : listeners_)
243 listener->endModulationMap();
244 }
245 else if (!e.mods.isRightButtonDown()) {
246 for (Listener* listener : listeners_)
247 listener->modulationClicked(this);
248 }
249 setMouseCursor(MouseCursor::ParentCursor);
250
251 mouse_state_ = kHover;
252}
253
254void ModulationButton::mouseEnter(const MouseEvent& e) {
255 mouse_state_ = kHover;
256 drag_drop_color_ = findColour(Skin::kLightenScreen, true);
257 show_drag_drop_ = parent_->getSynth()->getSourceConnections(getName().toStdString()).empty();
258 setActive(show_drag_drop_);
259 redrawImage(true);
260}
261
262void ModulationButton::mouseExit(const MouseEvent& e) {
263 mouse_state_ = kNone;
264 show_drag_drop_ = false;
265}
266
267void ModulationButton::mouseWheelMove(const MouseEvent& e, const MouseWheelDetails& wheel) {
268 for (Listener* listener : listeners_)
269 listener->modulationWheelMoved(e, wheel);
270}
271
272void ModulationButton::focusLost(FocusChangeType cause) {
273 for (Listener* listener : listeners_)
274 listener->modulationLostFocus(this);
275}
276
278 listeners_.push_back(listener);
279}
280
282 if (parent_ == nullptr)
283 return;
284
285 std::vector<vital::ModulationConnection*> connections =
286 parent_->getSynth()->getSourceConnections(getName().toStdString());
287
288 if (index == kDisconnect) {
289 for (vital::ModulationConnection* connection : connections)
290 disconnectModulation(connection);
291 }
292 else if (index >= kModulationList) {
293 int connection_index = index - kModulationList;
294 disconnectModulation(connections[connection_index]);
295 }
296}
297
298void ModulationButton::select(bool select) {
299 selected_ = select;
301}
302
304 active_modulation_ = active;
306}
307
309 if (parent_)
310 parent_->getSynth()->forceShowModulation(getName().toStdString(), active_modulation_);
311}
312
313void ModulationButton::disconnectModulation(vital::ModulationConnection* connection) {
314 int modulations_left = parent_->getSynth()->getNumModulations(connection->destination_name);
315
316 for (Listener* listener : listeners_) {
317 listener->modulationDisconnected(connection, modulations_left <= 1);
318 listener->modulationConnectionChanged();
319 }
320
321 parent_->disconnectModulation(connection);
322
323 if (modulations_left <= 1) {
324 for (Listener* listener : listeners_)
325 listener->modulationCleared();
326 }
327}
328
static Fonts * instance()
Gets the singleton instance of the Fonts class.
Definition fonts.h:52
Interface for receiving notifications about modulation events from the ModulationButton.
Definition modulation_button.h:60
ModulationButton(String name)
Constructs a ModulationButton.
Definition modulation_button.cpp:9
void select(bool select)
Selects or deselects this modulation source.
Definition modulation_button.cpp:298
void mouseDrag(const MouseEvent &e) override
Handles mouse drag events to start modulation mapping or continue dragging.
Definition modulation_button.cpp:221
static constexpr float kMeterAreaRatio
Ratio of the meter area to the total width.
Definition modulation_button.h:32
void setForceEnableModulationSource()
Forces the enable state of this modulation source in the parent synthesizer interface.
Definition modulation_button.cpp:308
void disconnectIndex(int index)
Disconnects a modulation connection at a given index in the popup menu.
Definition modulation_button.cpp:281
void mouseWheelMove(const MouseEvent &e, const MouseWheelDetails &wheel) override
Handles mouse wheel events to adjust modulation parameters.
Definition modulation_button.cpp:267
void resized() override
Called when the component is resized. Repositions internal sub-components.
Definition modulation_button.cpp:145
void setActiveModulation(bool active)
Sets whether this modulation source is actively modulating something.
Definition modulation_button.cpp:303
Rectangle< int > getMeterBounds()
Gets the bounding area used for a modulation meter.
Definition modulation_button.cpp:51
void mouseUp(const MouseEvent &e) override
Handles mouse up events to finalize selection or modulation mappings.
Definition modulation_button.cpp:240
virtual void render(OpenGlWrapper &open_gl, bool animate) override
Renders the drag-drop shape overlay using OpenGL.
Definition modulation_button.cpp:161
void mouseExit(const MouseEvent &e) override
Handles mouse exit events to hide the drag-drop overlay.
Definition modulation_button.cpp:262
void addListener(Listener *listener)
Adds a Listener to receive events from this ModulationButton.
Definition modulation_button.cpp:277
static constexpr int kModulationKnobColumns
Number of modulation knob columns.
Definition modulation_button.h:26
void mouseEnter(const MouseEvent &e) override
Handles mouse enter events to show the drag-drop overlay.
Definition modulation_button.cpp:254
static constexpr float kFontAreaHeightRatio
Ratio of the component's width used to display the text (font area).
Definition modulation_button.h:24
void mouseDown(const MouseEvent &e) override
Handles mouse down events to select or show disconnect menus.
Definition modulation_button.cpp:186
Rectangle< int > getModulationAmountBounds(int index, int total)
Gets the bounds for a modulation amount knob at a given index.
Definition modulation_button.cpp:37
void paintBackground(Graphics &g) override
Paints the background of the button, including meter areas and text.
Definition modulation_button.cpp:85
bool hasAnyModulation()
Checks if this modulation source is currently connected to any parameters.
Definition modulation_button.cpp:31
@ kNone
Definition modulation_button.h:49
@ kMouseDown
Definition modulation_button.h:51
@ kHover
Definition modulation_button.h:50
@ kMouseDragging
Definition modulation_button.h:52
@ kDraggingOut
Definition modulation_button.h:53
@ kDisconnect
Definition modulation_button.h:40
@ kModulationList
Definition modulation_button.h:41
void focusLost(FocusChangeType cause) override
Called when the component loses keyboard focus.
Definition modulation_button.cpp:272
Rectangle< int > getModulationAreaBounds()
Gets the bounding area used for modulation knobs.
Definition modulation_button.cpp:60
virtual ~ModulationButton()
Destructor.
Definition modulation_button.cpp:26
void parentHierarchyChanged() override
Called when the component's parent hierarchy changes.
Definition modulation_button.cpp:138
static String getUiSourceDisplayName(const String &original)
Returns a display name suitable for the UI given a source string.
Definition modulation_matrix.cpp:529
virtual void render(OpenGlWrapper &open_gl, bool animate)=0
Pure virtual function to render the component using OpenGL.
virtual void resized() override
Called when the component is resized.
Definition open_gl_component.cpp:121
void setComponent(Component *component)
Sets the component to be drawn into the OpenGL image. If not set, uses this component.
Definition open_gl_image_component.h:83
void setActive(bool active)
Sets whether this component is active (rendered) or not.
Definition open_gl_image_component.h:113
void setUseAlpha(bool use_alpha)
Enables or disables alpha blending for the image.
Definition open_gl_image_component.h:95
virtual void redrawImage(bool force)
Redraws the image if necessary, creating or updating the internal Image.
Definition open_gl_image_component.cpp:16
void setColor(Colour color)
Sets a color tint for the image.
Definition open_gl_image_component.h:101
static Path dragDropArrows()
Creates a drag-and-drop arrows icon path.
Definition paths.h:211
A component that draws a shape into an OpenGlImageComponent.
Definition open_gl_image_component.h:417
void setShape(Path shape)
Sets the shape to be drawn.
Definition open_gl_image_component.h:441
@ kWidgetMargin
Definition skin.h:103
@ kBodyRounding
Definition skin.h:71
@ kModulationButtonSelected
Definition skin.h:179
@ kBorder
Definition skin.h:134
@ kWidgetBackground
Definition skin.h:173
@ kBodyText
Definition skin.h:133
@ kLightenScreen
Definition skin.h:141
@ kModulationButtonUnselected
Definition skin.h:181
@ kShadow
Definition skin.h:142
std::vector< vital::ModulationConnection * > getSourceConnections(const std::string &source)
Returns all modulation connections from a particular source.
Definition synth_base.cpp:236
bool isSourceConnected(const std::string &source)
Checks if a given modulation source has any active connections.
Definition synth_base.cpp:245
void forceShowModulation(const std::string &source, bool force)
Forces a modulation source to remain active even if not currently connected.
Definition synth_base.cpp:216
int getNumModulations(const std::string &destination)
Counts how many modulations target a given parameter.
Definition synth_base.cpp:227
SynthBase * getSynth()
Returns the SynthBase instance this interface is managing.
Definition synth_gui_interface.h:85
void disconnectModulation(std::string source, std::string destination)
Disconnects a modulation from the GUI layer.
Definition synth_gui_interface.cpp:146
Base class for all synthesizer sections, providing UI layout, painting, and interaction logic.
Definition synth_section.h:193
void showPopupSelector(Component *source, Point< int > position, const PopupItems &options, std::function< void(int)> callback, std::function< void()> cancel={ })
Shows a popup selector with options.
Definition synth_section.cpp:119
float findValue(Skin::ValueId value_id) const
Finds a value in the skin overrides or from the parent if not found locally.
Definition synth_section.cpp:18
static std::string getDisplayName(const std::string &name)
Definition synth_parameters.h:212
A helper struct containing references to OpenGL context, shaders, and display scale.
Definition shaders.h:174
A hierarchical structure of popup menu items for a selector component.
Definition synth_section.h:29
void addItem(int sub_id, const std::string &sub_name, bool sub_selected=false)
Adds a new item as a submenu entry.
Definition synth_section.h:51
A structure representing a single modulation connection between a modulation source and a destination...
Definition synth_types.h:30
std::string destination_name
The name of the destination parameter.
Definition synth_types.h:74