@@ -271,6 +271,21 @@ struct Rect { | |||||
r.pos.y = clampf(pos.y, bound.pos.y, bound.pos.y + bound.size.y - size.y); | r.pos.y = clampf(pos.y, bound.pos.y, bound.pos.y + bound.size.y - size.y); | ||||
return r; | return r; | ||||
} | } | ||||
/** Expands this Rect to contain `other` */ | |||||
Rect expand(Rect other) { | |||||
Rect r; | |||||
r.pos.x = fminf(pos.x, other.pos.x); | |||||
r.pos.y = fminf(pos.y, other.pos.y); | |||||
r.size.x = fmaxf(pos.x + size.x, other.pos.x + other.size.x) - r.pos.x; | |||||
r.size.y = fmaxf(pos.y + size.y, other.pos.y + other.size.y) - r.pos.y; | |||||
return r; | |||||
} | |||||
/** Returns a Rect with its position set to zero */ | |||||
Rect zeroPos() { | |||||
Rect r; | |||||
r.size = size; | |||||
return r; | |||||
} | |||||
}; | }; | ||||
@@ -56,8 +56,13 @@ struct Widget { | |||||
virtual ~Widget(); | virtual ~Widget(); | ||||
Vec getAbsolutePos(); | |||||
Rect getChildrenBoundingBox(); | Rect getChildrenBoundingBox(); | ||||
/** Returns `v` transformed into the coordinate system of `relative` */ | |||||
virtual Vec getRelativeOffset(Vec v, Widget *relative); | |||||
/** Returns `v` transformed into world coordinates */ | |||||
Vec getAbsoluteOffset(Vec v) { | |||||
return getRelativeOffset(v, NULL); | |||||
} | |||||
/** Returns a subset of the given Rect bounded by the box of this widget and all ancestors */ | /** Returns a subset of the given Rect bounded by the box of this widget and all ancestors */ | ||||
virtual Rect getViewport(Rect r); | virtual Rect getViewport(Rect r); | ||||
@@ -149,6 +154,7 @@ struct TransformWidget : Widget { | |||||
struct ZoomWidget : Widget { | struct ZoomWidget : Widget { | ||||
float zoom = 1.0; | float zoom = 1.0; | ||||
Vec getRelativeOffset(Vec v, Widget *relative) override; | |||||
Rect getViewport(Rect r) override; | Rect getViewport(Rect r) override; | ||||
void setZoom(float zoom); | void setZoom(float zoom); | ||||
void draw(NVGcontext *vg) override; | void draw(NVGcontext *vg) override; | ||||
@@ -6,7 +6,7 @@ namespace rack { | |||||
void RackScrollWidget::step() { | void RackScrollWidget::step() { | ||||
Vec pos = gMousePos.minus(getAbsolutePos()); | |||||
Vec pos = gRackWidget->lastMousePos; | |||||
// Scroll rack if dragging cable near the edge of the screen | // Scroll rack if dragging cable near the edge of the screen | ||||
if (gRackWidget->wireContainer->activeWire) { | if (gRackWidget->wireContainer->activeWire) { | ||||
float margin = 20.0; | float margin = 20.0; | ||||
@@ -39,7 +39,7 @@ struct QuitItem : MenuItem { | |||||
struct FileChoice : ChoiceButton { | struct FileChoice : ChoiceButton { | ||||
void onAction() override { | void onAction() override { | ||||
Menu *menu = gScene->createMenu(); | Menu *menu = gScene->createMenu(); | ||||
menu->box.pos = getAbsolutePos().plus(Vec(0, box.size.y)); | |||||
menu->box.pos = getAbsoluteOffset(Vec(0, box.size.y)); | |||||
menu->box.size.x = box.size.x; | menu->box.size.x = box.size.x; | ||||
{ | { | ||||
@@ -70,7 +70,7 @@ struct SampleRateItem : MenuItem { | |||||
struct SampleRateChoice : ChoiceButton { | struct SampleRateChoice : ChoiceButton { | ||||
void onAction() override { | void onAction() override { | ||||
Menu *menu = gScene->createMenu(); | Menu *menu = gScene->createMenu(); | ||||
menu->box.pos = getAbsolutePos().plus(Vec(0, box.size.y)); | |||||
menu->box.pos = getAbsoluteOffset(Vec(0, box.size.y)); | |||||
menu->box.size.x = box.size.x; | menu->box.size.x = box.size.x; | ||||
PauseItem *pauseItem = new PauseItem(); | PauseItem *pauseItem = new PauseItem(); | ||||
@@ -124,31 +124,27 @@ void WireWidget::updateWire() { | |||||
} | } | ||||
Vec WireWidget::getOutputPos() { | Vec WireWidget::getOutputPos() { | ||||
Vec pos; | |||||
if (outputPort) { | if (outputPort) { | ||||
pos = Rect(outputPort->getAbsolutePos(), outputPort->box.size).getCenter(); | |||||
return outputPort->getRelativeOffset(outputPort->box.zeroPos().getCenter(), gRackWidget); | |||||
} | } | ||||
else if (hoveredOutputPort) { | else if (hoveredOutputPort) { | ||||
pos = Rect(hoveredOutputPort->getAbsolutePos(), hoveredOutputPort->box.size).getCenter(); | |||||
return hoveredOutputPort->getRelativeOffset(hoveredOutputPort->box.zeroPos().getCenter(), gRackWidget); | |||||
} | } | ||||
else { | else { | ||||
return gRackWidget->lastMousePos; | return gRackWidget->lastMousePos; | ||||
} | } | ||||
return pos.minus(getAbsolutePos().minus(box.pos)); | |||||
} | } | ||||
Vec WireWidget::getInputPos() { | Vec WireWidget::getInputPos() { | ||||
Vec pos; | |||||
if (inputPort) { | if (inputPort) { | ||||
pos = Rect(inputPort->getAbsolutePos(), inputPort->box.size).getCenter(); | |||||
return inputPort->getRelativeOffset(inputPort->box.zeroPos().getCenter(), gRackWidget); | |||||
} | } | ||||
else if (hoveredInputPort) { | else if (hoveredInputPort) { | ||||
pos = Rect(hoveredInputPort->getAbsolutePos(), hoveredInputPort->box.size).getCenter(); | |||||
return hoveredInputPort->getRelativeOffset(hoveredInputPort->box.zeroPos().getCenter(), gRackWidget); | |||||
} | } | ||||
else { | else { | ||||
return gRackWidget->lastMousePos; | return gRackWidget->lastMousePos; | ||||
} | } | ||||
return pos.minus(getAbsolutePos().minus(box.pos)); | |||||
} | } | ||||
void WireWidget::draw(NVGcontext *vg) { | void WireWidget::draw(NVGcontext *vg) { | ||||
@@ -335,7 +335,7 @@ struct AudioChoice : ChoiceButton { | |||||
AudioInterface *audioInterface; | AudioInterface *audioInterface; | ||||
void onAction() override { | void onAction() override { | ||||
Menu *menu = gScene->createMenu(); | Menu *menu = gScene->createMenu(); | ||||
menu->box.pos = getAbsolutePos().plus(Vec(0, box.size.y)); | |||||
menu->box.pos = getAbsoluteOffset(Vec(0, box.size.y)).round(); | |||||
menu->box.size.x = box.size.x; | menu->box.size.x = box.size.x; | ||||
int deviceCount = audioInterface->getDeviceCount(); | int deviceCount = audioInterface->getDeviceCount(); | ||||
@@ -373,7 +373,7 @@ struct SampleRateChoice : ChoiceButton { | |||||
AudioInterface *audioInterface; | AudioInterface *audioInterface; | ||||
void onAction() override { | void onAction() override { | ||||
Menu *menu = gScene->createMenu(); | Menu *menu = gScene->createMenu(); | ||||
menu->box.pos = getAbsolutePos().plus(Vec(0, box.size.y)); | |||||
menu->box.pos = getAbsoluteOffset(Vec(0, box.size.y)).round(); | |||||
menu->box.size.x = box.size.x; | menu->box.size.x = box.size.x; | ||||
const float sampleRates[6] = {44100, 48000, 88200, 96000, 176400, 192000}; | const float sampleRates[6] = {44100, 48000, 88200, 96000, 176400, 192000}; | ||||
@@ -404,7 +404,7 @@ struct BlockSizeChoice : ChoiceButton { | |||||
AudioInterface *audioInterface; | AudioInterface *audioInterface; | ||||
void onAction() override { | void onAction() override { | ||||
Menu *menu = gScene->createMenu(); | Menu *menu = gScene->createMenu(); | ||||
menu->box.pos = getAbsolutePos().plus(Vec(0, box.size.y)); | |||||
menu->box.pos = getAbsoluteOffset(Vec(0, box.size.y)).round(); | |||||
menu->box.size.x = box.size.x; | menu->box.size.x = box.size.x; | ||||
const int blockSizes[] = {64, 128, 256, 512, 1024, 2048, 4096}; | const int blockSizes[] = {64, 128, 256, 512, 1024, 2048, 4096}; | ||||
@@ -201,7 +201,7 @@ struct ClockRatioChoice : ChoiceButton { | |||||
void onAction() { | void onAction() { | ||||
Menu *menu = gScene->createMenu(); | Menu *menu = gScene->createMenu(); | ||||
menu->box.pos = getAbsolutePos().plus(Vec(0, box.size.y)); | |||||
menu->box.pos = getAbsoluteOffset(Vec(0, box.size.y)).round(); | |||||
menu->box.size.x = box.size.x; | menu->box.size.x = box.size.x; | ||||
for (unsigned long ratio = 0; ratio < ratioNames.size(); ratio++) { | for (unsigned long ratio = 0; ratio < ratioNames.size(); ratio++) { | ||||
@@ -182,7 +182,7 @@ void MidiItem::onAction() { | |||||
void MidiChoice::onAction() { | void MidiChoice::onAction() { | ||||
Menu *menu = gScene->createMenu(); | Menu *menu = gScene->createMenu(); | ||||
menu->box.pos = getAbsolutePos().plus(Vec(0, box.size.y)); | |||||
menu->box.pos = getAbsoluteOffset(Vec(0, box.size.y)).round(); | |||||
menu->box.size.x = box.size.x; | menu->box.size.x = box.size.x; | ||||
{ | { | ||||
@@ -217,7 +217,7 @@ void ChannelItem::onAction() { | |||||
void ChannelChoice::onAction() { | void ChannelChoice::onAction() { | ||||
Menu *menu = gScene->createMenu(); | Menu *menu = gScene->createMenu(); | ||||
menu->box.pos = getAbsolutePos().plus(Vec(0, box.size.y)); | |||||
menu->box.pos = getAbsoluteOffset(Vec(0, box.size.y)).round(); | |||||
menu->box.size.x = box.size.x; | menu->box.size.x = box.size.x; | ||||
{ | { | ||||
@@ -259,7 +259,7 @@ struct ModeChoice : ChoiceButton { | |||||
void onAction() { | void onAction() { | ||||
Menu *menu = gScene->createMenu(); | Menu *menu = gScene->createMenu(); | ||||
menu->box.pos = getAbsolutePos().plus(Vec(0, box.size.y)); | |||||
menu->box.pos = getAbsoluteOffset(Vec(0, box.size.y)).round(); | |||||
menu->box.size.x = box.size.x; | menu->box.size.x = box.size.x; | ||||
for (unsigned long i = 0; i < modeNames.size(); i++) { | for (unsigned long i = 0; i < modeNames.size(); i++) { | ||||
@@ -6,7 +6,7 @@ namespace rack { | |||||
void Tooltip::step() { | void Tooltip::step() { | ||||
// Follow the mouse | // Follow the mouse | ||||
box.pos = gMousePos.minus(parent->getAbsolutePos()); | |||||
box.pos = gMousePos; | |||||
// Wrap size to contents | // Wrap size to contents | ||||
// box.size = getChildrenBoundingBox().getBottomRight(); | // box.size = getChildrenBoundingBox().getBottomRight(); | ||||
@@ -16,28 +16,28 @@ Widget::~Widget() { | |||||
clearChildren(); | clearChildren(); | ||||
} | } | ||||
Vec Widget::getAbsolutePos() { | |||||
// Recursively compute position offset from parents | |||||
if (!parent) { | |||||
return box.pos; | |||||
} | |||||
else { | |||||
return box.pos.plus(parent->getAbsolutePos()); | |||||
Rect Widget::getChildrenBoundingBox() { | |||||
Rect bound; | |||||
for (Widget *child : children) { | |||||
if (child == children.front()) { | |||||
bound = child->box; | |||||
} | |||||
else { | |||||
bound = bound.expand(child->box); | |||||
} | |||||
} | } | ||||
return bound; | |||||
} | } | ||||
Rect Widget::getChildrenBoundingBox() { | |||||
if (children.empty()) { | |||||
return Rect(); | |||||
Vec Widget::getRelativeOffset(Vec v, Widget *relative) { | |||||
if (this == relative) { | |||||
return v; | |||||
} | } | ||||
Vec topLeft = Vec(INFINITY, INFINITY); | |||||
Vec bottomRight = Vec(-INFINITY, -INFINITY); | |||||
for (Widget *child : children) { | |||||
topLeft = topLeft.min(child->box.pos); | |||||
bottomRight = bottomRight.max(child->box.getBottomRight()); | |||||
v = v.plus(box.pos); | |||||
if (parent) { | |||||
v = parent->getRelativeOffset(v, relative); | |||||
} | } | ||||
return Rect(topLeft, bottomRight.minus(topLeft)); | |||||
return v; | |||||
} | } | ||||
Rect Widget::getViewport(Rect r) { | Rect Widget::getViewport(Rect r) { | ||||
@@ -3,6 +3,11 @@ | |||||
namespace rack { | namespace rack { | ||||
Vec ZoomWidget::getRelativeOffset(Vec v, Widget *relative) { | |||||
return Widget::getRelativeOffset(v.mult(zoom), relative); | |||||
} | |||||
Rect ZoomWidget::getViewport(Rect r) { | Rect ZoomWidget::getViewport(Rect r) { | ||||
r.pos = r.pos.mult(zoom); | r.pos = r.pos.mult(zoom); | ||||
r.size = r.size.mult(zoom); | r.size = r.size.mult(zoom); | ||||