I wonder if it’s possible to have 2 completely overlapping (spatially congruent) SliderKnobs with the following behavior:
one knob responds only to vertical mouse dragging, the other only to horizontal dragging
all mouse actions are passed to both SliderKnobs, so that one will adjust its associated parameter on vertical dragging, while the other will adjust its parameter on horizontal dragging
context: an LFO-generating module with a display of the LFO signal, such that dragging on the display vertically should adjust offset, while dragging horizontally should adjust scale of the signal
I’m a newbie so answers showing as much code as possible will be highly appreciated.
I’ve scraped as much knowledge as I could from the Rack API, from existing code by other module developers and from Leonardo Gabrielli’s book “Developing virtual synthesizers with VCV rack”. I’ve managed to make the knobs and place them on top of each other, either as silbings or parent-child widgets. Then I tried to adapt the OnMouseDrag() function, but no matter what I do, any mouse actions are only ever handled by the top one of the two widgets.
This is likely because one of the widgets is “consuming” the event, so it is no longer available by the other widget. Take a look at OpaqueWidget::onDragHover, from which the Knob class derives. The lines of code like e.Consume(this) indicate that the event has been claimed and will be ignored by other children of the parent widget.
Making it do what you want should be possible, but it will take some careful study of the code, and you may have to define a subclass of Knob and redefine the behavior of onDragHover and possibly other functions too.
for (auto it = children.rbegin(); it != children.rend(); it++) {
// Stop propagation if requested
if (!e.isPropagating())
break;
Widget* child = *it;
// Filter child by visibility and position
if (!child->visible)
continue;
if (!child->box.contains(e.pos))
continue;
// Clone event and adjust its position
TEvent e2 = e;
e2.pos = e.pos.minus(child->box.pos);
// Call child event handler
(child->*f)(e2);
e.unconsume();
}
Edit: Well you can probably simplify since it’s just a certain kind of event, so no need to dynamically call the method.
I assume it is short hand for (*(child->f))(e2) which dereferences child for the pointer to pointer to function and runs the function with parameter e2 or such.
I managed to find a solution. Don’t know how elegant it is, but it works.
So here
I define two slider knobs that are completely overlapping (spatially congruent)
add them as children to the ModuleWidget
add code to the OnDragMove() method of the top slider knob to make both slider knobs respond to the mouse dragging
make one knob respond only to vertical, the other only to horizontal dragging
// Struct for Module Widget
struct VCA_1_CopyWidget : ModuleWidget {
VCA_1_CopyWidget(VCA_1_Copy* module) {
setModule(module);
// ...add screws, inputs, outputs etc. here...
// slider knob that goes on top
VCA_1_CopyVUKnob* knob = createParam<VCA_1_CopyVUKnob>(mm2px(Vec(2.253, 15.931)), module, VCA_1_Copy::LEVEL_PARAM);
knob->box.size = mm2px(Vec(10, 50.253));
// respond to vertical dragging only
knob->horizontal = false;
addChild(knob);
// slider knob that goes below
VCA_1_CopyVUKnob2* knob2 = createParam<VCA_1_CopyVUKnob2>(mm2px(Vec(2.253, 15.931)), module, VCA_1_Copy::LEVEL_PARAM);
// this knob is spatially congruent with the other (same size and position)
knob2->box.size = mm2px(Vec(10, 50.253));
// respond to horizontal dragging only
knob2->horizontal = true;
// this knob goes to the bottom, so that it can later be
// easily identified in the list of children of the ModuleWidget
addChildBottom(knob2);
}
}
// Struct for top slider knob
struct VCA_1_CopyVUKnob : SliderKnob {
void drawLayer(const DrawArgs& args, int layer) override {
// ...drawLayer code...
}
void onDragMove(const DragMoveEvent& e) override {
// get BOTTOM slider knob as a child of the ModuleWidget ("this->getParent()")
// since we put it at the bottom, it will be the first item
// in the children list ("children.front()")
Widget* child = this->getParent()->children.front();
// make the bottom slider knob respond to the mouse drag event as well
(child->onDragMove)(e);
SliderKnob::onDragMove(e);
}
};
// Struct for bottom slider knob
struct VCA_1_CopyVUKnob2 : SliderKnob {
void drawLayer(const DrawArgs& args, int layer) override {
// ...drawLayer code...
}
void onDragMove(const DragMoveEvent& e) override {
SliderKnob::onDragMove(e);
}
};
If you see any mistakes or improvements, let me know!