Transform Widget which transforms Mouse

I’ve previously had an issue with the my Submarine TD-202 vertical text label. You couldn’t select text easily with it, because it didn’t correctly match the mouse movement to the display.

I’ve finally resolved that by using a TransformWidget to do the rotation, but the built-in TransformWidget does only transforms the nanovg graphics context, not the mouse position. Mostly often this is correct, the TransformWidget is used on rotating knobs to rotate the SvgWidget which displays the knob, and you don’t want the up-and-down movement of the cursor to change orientation as you turn the knob.

But for this use-case I created a MouseTransformWidget which does map the mouse movement to the visuals.

struct MouseTransformWidget:VirtualWidget {
        float transform[6];
        float inverse[6];
        float invLinear[6];
        int hasInverse;
        MouseTransformWidget() {
                identity();
        }
        Rect getChildrenBoundingBox() override {
                Rect bound = Widget::getChildrenBoundingBox();
                Vec topLeft = bound.pos;
                Vec bottomRight = bound.getBottomRight();
                nvgTransformPoint(&topLeft.x, &topLeft.y, transform, topLeft.x, topLeft.y);
                nvgTransformPoint(&bottomRight.x, &bottomRight.y, transform, bottomRight.x, bottomRight.y);
                return Rect(topLeft, bottomRight.minus(topLeft));
        }
        void identity() {
                nvgTransformIdentity(transform);
                nvgTransformIdentity(inverse);
                std::memcpy(invLinear, inverse, 4 * sizeof(float));
                invLinear[4] = invLinear[5] = 0.0;
                hasInverse = true;
        }
        void translate(Vec delta) {
                float t[6];
                nvgTransformTranslate(t, delta.x, delta.y);
                nvgTransformPremultiply(transform, t);
                hasInverse = nvgTransformInverse(inverse, transform);
                std::memcpy(invLinear, inverse, 4 * sizeof(float));
        }
        void rotate(float angle) {
                float t[6];
                nvgTransformRotate(t, angle);
                nvgTransformPremultiply(transform, t);
                hasInverse = nvgTransformInverse(inverse, transform);
                std::memcpy(invLinear, inverse, 4 * sizeof(float));
        }
        void scale(Vec s) {
                float t[6];
                nvgTransformScale(t, s.x, s.y);
                nvgTransformPremultiply(transform, t);
                hasInverse = nvgTransformInverse(inverse, transform);
                std::memcpy(invLinear, inverse, 4 * sizeof(float));
        }
        void draw(NVGcontext *vg) override {
                // No need to save the state because that is done in the parent
                nvgTransform(vg, transform[0], transform[1], transform[2], transform[3], transform[4], transform[5]);
                Widget::draw(vg);
        }
        void onMouseDown(EventMouseDown &e) override {
                Vec pos = e.pos;
                if (hasInverse) {
                        nvgTransformPoint(&e.pos.x, &e.pos.y, inverse, e.pos.x, e.pos.y);
                }
                Widget::onMouseDown(e);
                e.pos = pos;
        }
        void onMouseUp(EventMouseUp &e) override {
                Vec pos = e.pos;
                if (hasInverse) {
                        nvgTransformPoint(&e.pos.x, &e.pos.y, inverse, e.pos.x, e.pos.y);
                }
                Widget::onMouseUp(e);
                e.pos = pos;
        }
        void onMouseMove(EventMouseMove &e) override {
                Vec pos = e.pos;
                Vec mouseRel = e.mouseRel;
                if (hasInverse) {
                        nvgTransformPoint(&e.pos.x, &e.pos.y, inverse, e.pos.x, e.pos.y);
                        nvgTransformPoint(&e.mouseRel.x, &e.mouseRel.y, invLinear, e.mouseRel.x, e.mouseRel.y);
                }
                Widget::onMouseMove(e);
                e.pos = pos;
                e.mouseRel = mouseRel;
        }
        void onHoverKey(EventHoverKey &e) override {
                Vec pos = e.pos;
                if (hasInverse) {
                        nvgTransformPoint(&e.pos.x, &e.pos.y, inverse, e.pos.x, e.pos.y);
                }
                Widget::onHoverKey(e);
                e.pos = pos;
        }
        void onScroll(EventScroll &e) override {
                Vec pos = e.pos;
                Vec scrollRel = e.scrollRel;
                if (hasInverse) {
                        nvgTransformPoint(&e.pos.x, &e.pos.y, inverse, e.pos.x, e.pos.y);
                        nvgTransformPoint(&e.scrollRel.x, &e.scrollRel.y, invLinear, e.scrollRel.x, e.scrollRel.y);
                }
                Widget::onScroll(e);
                e.pos = pos;
                e.scrollRel = scrollRel;
        }
        void onPathDrop(EventPathDrop &e) override {
                Vec pos = e.pos;
                if (hasInverse) {
                        nvgTransformPoint(&e.pos.x, &e.pos.y, inverse, e.pos.x, e.pos.y);
                }
                Widget::onPathDrop(e);
                e.pos = pos;
        }
};

You could probably just inherit from TransformWidget to write maybe 50% of the code.

I wasn’t sure about overriding the non-virtual methods. C++ is still fairly new to me.

I could have used different method names instead, but it felt ugly.

Speaking as a veteran C++ developer – not a VCV Rack developer – I can’t think of a use-case for non-virtual functions in a class which is meant to be a parent class.

If you derive a new class from a parent class, and redefine non-virtual methods, the parent class version of those methods will be called, unless you’re calling through a pointer to the child class.

It is a cause of mysterious bugs.

If my explanation is confusing, here’s a code example that generates this output:

./nonvirt
hello from Child::vfun
hello from Child::nonvfun
hello from Child::vfun
hello from Parent::nonvfun

#include

class Parent {
public:
virtual void vfun() { std::cout << “hello from Parent::vfun” << std::endl; }
void nonvfun() { std::cout << “hello from Parent::nonvfun” << std::endl; }
};

class Child : public Parent {
public:
virtual void vfun() { std::cout << “hello from Child::vfun” << std::endl; }
void nonvfun() { std::cout << “hello from Child::nonvfun” << std::endl; }
};

int main() {
Child *childptr(new Child);
Parent *parentptr(childptr);
childptr->vfun();
childptr->nonvfun();
parentptr->vfun();
parentptr->nonvfun();
delete childptr;
return 0;
}

1 Like

Also, you need virtual destructors :wink:

1 Like

You shouldn’t need to override nonvirtual methods. Subclass TransformWidget and override all relevant on*() position events. Use TransformWidget‘s transform field to make transformations on the events’ positions, and you’re done. The nonvirtual scale, rotate, etc methods should work unmodified.

I see what you mean, but it’s not trivial to calculate the inverse matrix. I thought it better to do that as the transform changes rather than onDraw. Which is why I would have tapped into the rotate and scale methods if they were virtual.

@chaircrusher those were exactly the concerns I had about subclassing non-virtual. Thank you for confirming my understanding.