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;
}
};