Pass mouse actions to 2 completely overlapping SliderKnobs?

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.

Thanks

I don’t know the Rack api.

Maybe you can use some of the code in this module ?

1 Like

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.

Something like this

		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.

Thank you! If you have a minute:

  • where would this code go?
  • what does the “*f” in “(child->*f)(e2);” refer to?

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!