diff --git a/helper.py b/helper.py index dca11501..5ddf2a44 100755 --- a/helper.py +++ b/helper.py @@ -7,9 +7,8 @@ import json import xml.etree.ElementTree -if sys.version_info < (3, 6): - print("Python 3.6 or higher required") - exit(1) +# Version check +f"Python 3.6 is required" class UserException(Exception): diff --git a/include/app/RackScrollWidget.hpp b/include/app/RackScrollWidget.hpp index 14ed7b44..8a344c5e 100644 --- a/include/app/RackScrollWidget.hpp +++ b/include/app/RackScrollWidget.hpp @@ -20,6 +20,7 @@ struct RackScrollWidget : ui::ScrollWidget { void draw(const DrawArgs &args) override; void onHover(const widget::HoverEvent &e) override; void onHoverScroll(const widget::HoverScrollEvent &e) override; + void reset(); }; diff --git a/include/app/common.hpp b/include/app/common.hpp index c76083ab..0dbd70a9 100644 --- a/include/app/common.hpp +++ b/include/app/common.hpp @@ -43,6 +43,7 @@ inline math::Vec mm2px(math::Vec mm) { static const float RACK_GRID_WIDTH = 15; static const float RACK_GRID_HEIGHT = 380; static const math::Vec RACK_GRID_SIZE = math::Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT); +static const math::Vec RACK_OFFSET = RACK_GRID_SIZE.mult(math::Vec(2000, 100)); } // namespace app diff --git a/include/math.hpp b/include/math.hpp index 9cb7cd7f..ba44e13f 100644 --- a/include/math.hpp +++ b/include/math.hpp @@ -15,25 +15,25 @@ namespace math { // basic integer functions //////////////////// -/** Returns true if x is odd */ +/** Returns true if x is odd. */ inline bool isEven(int x) { return x % 2 == 0; } -/** Returns true if x is odd */ +/** Returns true if x is odd. */ inline bool isOdd(int x) { return x % 2 != 0; } -/** Limits `x` between `a` and `b` -Assumes a <= b +/** Limits `x` between `a` and `b`. +If b < a, returns a. */ inline int clamp(int x, int a, int b) { - return std::min(std::max(x, a), b); + return std::max(std::min(x, b), a); } -/** Limits `x` between `a` and `b` -If a > b, switches the two values +/** Limits `x` between `a` and `b`. +If b < a, switches the two values. */ inline int clampSafe(int x, int a, int b) { return clamp(x, std::min(a, b), std::max(a, b)); @@ -72,8 +72,7 @@ inline void eucDivMod(int a, int b, int *div, int *mod) { } } -/** Returns floor(log_2(n)), or 0 if n == 1. -*/ +/** Returns floor(log_2(n)), or 0 if n == 1. */ inline int log2(int n) { int i = 0; while (n >>= 1) { @@ -82,7 +81,7 @@ inline int log2(int n) { return i; } -/** Returns whether `n` is a power of 2 */ +/** Returns whether `n` is a power of 2. */ inline bool isPow2(int n) { return n > 0 && (n & (n - 1)) == 0; } @@ -91,22 +90,22 @@ inline bool isPow2(int n) { // basic float functions //////////////////// -/** Limits `x` between `a` and `b` -Assumes a <= b +/** Limits `x` between `a` and `b`. +If b < a, returns a. */ inline float clamp(float x, float a, float b) { - return std::fmin(std::fmax(x, a), b); + return std::fmax(std::fmin(x, b), a); } -/** Limits `x` between `a` and `b` -If a > b, switches the two values +/** Limits `x` between `a` and `b`. +If b < a, switches the two values. */ inline float clampSafe(float x, float a, float b) { return clamp(x, std::fmin(a, b), std::fmax(a, b)); } -/** Returns 1 for positive numbers, -1 for negative numbers, and 0 for zero -See https://en.wikipedia.org/wiki/Sign_function +/** Returns 1 for positive numbers, -1 for negative numbers, and 0 for zero. +See https://en.wikipedia.org/wiki/Sign_function. */ inline float sgn(float x) { return x > 0.f ? 1.f : x < 0.f ? -1.f : 0.f; @@ -118,18 +117,19 @@ inline float normalizeZero(float x) { } /** Euclidean modulus. Always returns 0 <= mod < b. -See https://en.wikipedia.org/wiki/Euclidean_division +See https://en.wikipedia.org/wiki/Euclidean_division. */ inline float eucMod(float a, float base) { float mod = std::fmod(a, base); return (mod >= 0.f) ? mod : mod + base; } +/** Returns whether a is within epsilon distance from b. */ inline bool isNear(float a, float b, float epsilon = 1e-6f) { return std::fabs(a - b) <= epsilon; } -/** If the magnitude of x if less than epsilon, return 0 */ +/** If the magnitude of x if less than epsilon, return 0. */ inline float chop(float x, float epsilon = 1e-6f) { return isNear(x, 0.f, epsilon) ? 0.f : x; } @@ -142,7 +142,7 @@ inline float crossfade(float a, float b, float p) { return a + (b - a) * p; } -/** Linearly interpolate an array `p` with index `x` +/** Linearly interpolates an array `p` with index `x`. Assumes that the array at `p` is of length at least floor(x)+1. */ inline float interpolateLinear(const float *p, float x) { @@ -151,8 +151,8 @@ inline float interpolateLinear(const float *p, float x) { return crossfade(p[xi], p[xi+1], xf); } -/** Complex multiply c = a * b -Arguments may be the same pointers +/** Complex multiplies c = a * b. +Arguments may be the same pointers. i.e. cmultf(&ar, &ai, ar, ai, br, bi) */ inline void complexMult(float *cr, float *ci, float ar, float ai, float br, float bi) { @@ -173,7 +173,7 @@ struct Vec { Vec() {} Vec(float x, float y) : x(x), y(y) {} - /** Negates the vector + /** Negates the vector. Equivalent to a reflection across the y=-x line. */ Vec neg() const { @@ -206,13 +206,13 @@ struct Vec { float square() const { return x * x + y * y; } - /** Rotates counterclockwise in radians */ + /** Rotates counterclockwise in radians. */ Vec rotate(float angle) { float sin = std::sin(angle); float cos = std::cos(angle); return Vec(x * cos - y * sin, x * sin + y * cos); } - /** Swaps the coordinates + /** Swaps the coordinates. Equivalent to a reflection across the y=x line. */ Vec flip() const { @@ -256,22 +256,23 @@ struct Rect { Rect() {} Rect(Vec pos, Vec size) : pos(pos), size(size) {} - /** Constructs a Rect from the upper-left position `a` and lower-right pos `b` */ + Rect(float posX, float posY, float sizeX, float sizeY) : pos(math::Vec(posX, posY)), size(math::Vec(sizeX, sizeY)) {} + /** Constructs a Rect from the upper-left position `a` and lower-right pos `b`. */ static Rect fromMinMax(Vec a, Vec b) { return Rect(a, b.minus(a)); } - /** Returns whether this Rect contains an entire point, inclusive on the top/left, non-inclusive on the bottom/right */ + /** Returns whether this Rect contains an entire point, inclusive on the top/left, non-inclusive on the bottom/right. */ bool isContaining(Vec v) const { return pos.x <= v.x && v.x < pos.x + size.x && pos.y <= v.y && v.y < pos.y + size.y; } - /** Returns whether this Rect contains an entire Rect */ + /** Returns whether this Rect contains an entire Rect. */ bool isContaining(Rect r) const { return pos.x <= r.pos.x && r.pos.x + r.size.x <= pos.x + size.x && pos.y <= r.pos.y && r.pos.y + r.size.y <= pos.y + size.y; } - /** Returns whether this Rect overlaps with another Rect */ + /** Returns whether this Rect overlaps with another Rect. */ bool isIntersecting(Rect r) const { return (pos.x + size.x > r.pos.x && r.pos.x + r.size.x > pos.x) && (pos.y + size.y > r.pos.y && r.pos.y + r.size.y > pos.y); @@ -300,7 +301,7 @@ struct Rect { Vec getBottomRight() const { return pos.plus(size); } - /** Clamps the edges of the rectangle to fit within a bound */ + /** Clamps the edges of the rectangle to fit within a bound. */ Rect clamp(Rect bound) const { Rect r; r.pos.x = math::clampSafe(pos.x, bound.pos.x, bound.pos.x + bound.size.x); @@ -309,7 +310,7 @@ struct Rect { r.size.y = math::clamp(pos.y + size.y, bound.pos.y, bound.pos.y + bound.size.y) - r.pos.y; return r; } - /** Nudges the position to fix inside a bounding box */ + /** Nudges the position to fix inside a bounding box. */ Rect nudge(Rect bound) const { Rect r; r.size = size; @@ -317,7 +318,7 @@ struct Rect { r.pos.y = math::clampSafe(pos.y, bound.pos.y, bound.pos.y + bound.size.y - size.y); return r; } - /** Expands this Rect to contain `b` */ + /** Expands this Rect to contain `b`. */ Rect expand(Rect b) const { Rect r; r.pos.x = std::fmin(pos.x, b.pos.x); @@ -326,7 +327,7 @@ struct Rect { r.size.y = std::fmax(pos.y + size.y, b.pos.y + b.size.y) - r.pos.y; return r; } - /** Returns the intersection of `this` and `b` */ + /** Returns the intersection of `this` and `b`. */ Rect intersect(Rect b) const { Rect r; r.pos.x = std::fmax(pos.x, b.pos.x); @@ -335,11 +336,11 @@ struct Rect { r.size.y = std::fmin(pos.y + size.y, b.pos.y + b.size.y) - r.pos.y; return r; } - /** Returns a Rect with its position set to zero */ + /** Returns a Rect with its position set to zero. */ Rect zeroPos() const { return Rect(Vec(), size); } - /** Expands each corner + /** Expands each corner. Use a negative delta to shrink. */ Rect grow(Vec delta) const { @@ -371,7 +372,7 @@ inline Vec Vec::clampSafe(Rect bound) const { /** Expands a Vec and Rect into a comma-separated list. Useful for print debugging. - printf("%f %f %f %f", RECT_ARGS(r)); + printf("(%f %f) (%f %f %f %f)", VEC_ARGS(v), RECT_ARGS(r)); Or passing the values to a C function. diff --git a/src/app/RackRail.cpp b/src/app/RackRail.cpp index 13e251e1..ae522eef 100644 --- a/src/app/RackRail.cpp +++ b/src/app/RackRail.cpp @@ -47,15 +47,6 @@ void RackRail::draw(const DrawArgs &args) { nvgLineTo(args.vg, box.size.x, railY + RACK_GRID_HEIGHT - 0.5); nvgStroke(args.vg); } - - - // Useful for screenshots - if (0) { - nvgBeginPath(args.vg); - nvgRect(args.vg, 0.0, 0.0, box.size.x, box.size.y); - nvgFillColor(args.vg, nvgRGBf(1.0, 1.0, 1.0)); - nvgFill(args.vg); - } } diff --git a/src/app/RackScrollWidget.cpp b/src/app/RackScrollWidget.cpp index 768a2e0e..53439524 100644 --- a/src/app/RackScrollWidget.cpp +++ b/src/app/RackScrollWidget.cpp @@ -14,11 +14,14 @@ RackScrollWidget::RackScrollWidget() { container->addChild(zoomWidget); rackWidget = new RackWidget; + rackWidget->box.size = RACK_OFFSET.mult(2); zoomWidget->addChild(rackWidget); + + reset(); } void RackScrollWidget::step() { - float zoom = std::round(settings.zoom * 100) / 100; + float zoom = std::round(settings.zoom / 0.01) * 0.01; if (zoom != zoomWidget->zoom) { // Set offset based on zoomPos offset = offset.plus(zoomPos).div(zoomWidget->zoom).mult(zoom).minus(zoomPos); @@ -28,14 +31,20 @@ void RackScrollWidget::step() { zoomPos = box.size.div(2); - // Resize RackWidget to be a bit larger than the viewport - rackWidget->box.size = box.size - .minus(container->box.pos) - .plus(math::Vec(500, 500)) - .div(zoomWidget->zoom); - - // Resize ZoomWidget - zoomWidget->box.size = rackWidget->box.size.mult(zoomWidget->zoom); + // Set zoomWidget box to module bounding box + math::Rect moduleBox = rackWidget->moduleContainer->getChildrenBoundingBox(); + if (!moduleBox.size.isFinite()) + moduleBox = math::Rect(RACK_OFFSET, math::Vec(0, 0)); + zoomWidget->box.pos = moduleBox.pos.mult(zoomWidget->zoom); + zoomWidget->box.size = moduleBox.size.mult(zoomWidget->zoom); + // Expand to viewport + math::Rect viewportBox = box; + viewportBox.pos = viewportBox.pos.plus(offset); + zoomWidget->box = zoomWidget->box.expand(viewportBox); + // Grow a few pixels + zoomWidget->box = zoomWidget->box.grow(math::Vec(100, 100)); + // Reposition rackWidget + rackWidget->box.pos = zoomWidget->box.pos.div(zoomWidget->zoom).neg(); // Scroll rack if dragging cable near the edge of the screen math::Vec pos = APP->window->mousePos; @@ -58,6 +67,7 @@ void RackScrollWidget::step() { void RackScrollWidget::draw(const DrawArgs &args) { + // DEBUG("%f %f %f %f", RECT_ARGS(args.clipBox)); ScrollWidget::draw(args); } @@ -72,18 +82,14 @@ void RackScrollWidget::onHover(const widget::HoverEvent &e) { else if ((APP->window->getMods() & WINDOW_MOD_MASK) == GLFW_MOD_SHIFT) arrowSpeed /= 4.0; - if (glfwGetKey(APP->window->win, GLFW_KEY_LEFT) == GLFW_PRESS) { + if (glfwGetKey(APP->window->win, GLFW_KEY_LEFT) == GLFW_PRESS) offset.x -= arrowSpeed; - } - if (glfwGetKey(APP->window->win, GLFW_KEY_RIGHT) == GLFW_PRESS) { + if (glfwGetKey(APP->window->win, GLFW_KEY_RIGHT) == GLFW_PRESS) offset.x += arrowSpeed; - } - if (glfwGetKey(APP->window->win, GLFW_KEY_UP) == GLFW_PRESS) { + if (glfwGetKey(APP->window->win, GLFW_KEY_UP) == GLFW_PRESS) offset.y -= arrowSpeed; - } - if (glfwGetKey(APP->window->win, GLFW_KEY_DOWN) == GLFW_PRESS) { + if (glfwGetKey(APP->window->win, GLFW_KEY_DOWN) == GLFW_PRESS) offset.y += arrowSpeed; - } } ScrollWidget::onHover(e); @@ -105,6 +111,11 @@ void RackScrollWidget::onHoverScroll(const widget::HoverScrollEvent &e) { ScrollWidget::onHoverScroll(e); } +void RackScrollWidget::reset() { + offset = RACK_OFFSET.mult(zoomWidget->zoom); + offset = offset.minus(math::Vec(20, 20)); +} + } // namespace app } // namespace rack diff --git a/src/app/RackWidget.cpp b/src/app/RackWidget.cpp index a509e21a..6ecba782 100644 --- a/src/app/RackWidget.cpp +++ b/src/app/RackWidget.cpp @@ -97,13 +97,7 @@ RackWidget::~RackWidget() { } void RackWidget::step() { - // Expand size to fit modules - math::Vec moduleSize = moduleContainer->getChildrenBoundingBox().getBottomRight(); - // We assume that the size is reset by a parent before calling step(). Otherwise it will grow unbounded. - box.size = box.size.max(moduleSize); - // Adjust size and position of rails - widget::Widget *rail = rails->children.front(); math::Rect bound = getViewport(math::Rect(math::Vec(), box.size)); if (!rails->box.isContaining(bound)) { math::Vec cellMargin = math::Vec(20, 1); @@ -111,14 +105,15 @@ void RackWidget::step() { rails->box.size = bound.size.plus(cellMargin.mult(RACK_GRID_SIZE).mult(2)); rails->dirty = true; + RackRail *rail = rails->getFirstDescendantOfType(); rail->box.size = rails->box.size; } - Widget::step(); + OpaqueWidget::step(); } void RackWidget::draw(const DrawArgs &args) { - Widget::draw(args); + OpaqueWidget::draw(args); } void RackWidget::onHover(const widget::HoverEvent &e) { @@ -157,7 +152,6 @@ void RackWidget::onButton(const widget::ButtonEvent &e) { } void RackWidget::onZoom(const widget::ZoomEvent &e) { - rails->box.size = math::Vec(); OpaqueWidget::onZoom(e); } @@ -189,7 +183,8 @@ json_t *RackWidget::toJson() { // id json_object_set_new(moduleJ, "id", json_integer(moduleWidget->module->id)); // pos - math::Vec pos = moduleWidget->box.pos.div(RACK_GRID_SIZE).round(); + math::Vec pos = moduleWidget->box.pos.minus(RACK_OFFSET); + pos = pos.div(RACK_GRID_SIZE).round(); json_t *posJ = json_pack("[i, i]", (int) pos.x, (int) pos.y); json_object_set_new(moduleJ, "pos", posJ); } @@ -252,6 +247,7 @@ void RackWidget::fromJson(json_t *rootJ) { else { moduleWidget->box.pos = pos.mult(RACK_GRID_SIZE); } + moduleWidget->box.pos = moduleWidget->box.pos.plus(RACK_OFFSET); addModule(moduleWidget); } @@ -387,10 +383,6 @@ void RackWidget::removeModule(ModuleWidget *m) { } bool RackWidget::requestModuleBox(ModuleWidget *m, math::Rect requestedBox) { - // Check bounds - if (requestedBox.pos.x < 0 || requestedBox.pos.y < 0) - return false; - // Check intersection with other modules for (widget::Widget *m2 : moduleContainer->children) { // Don't intersect with self @@ -412,8 +404,8 @@ bool RackWidget::requestModuleBoxNearest(ModuleWidget *m, math::Rect requestedBo int x0 = std::round(requestedBox.pos.x / RACK_GRID_WIDTH); int y0 = std::round(requestedBox.pos.y / RACK_GRID_HEIGHT); std::vector positions; - for (int y = std::max(0, y0 - 8); y < y0 + 8; y++) { - for (int x = std::max(0, x0 - 400); x < x0 + 400; x++) { + for (int y = y0 - 4; y < y0 + 4; y++) { + for (int x = x0 - 200; x < x0 + 200; x++) { positions.push_back(math::Vec(x * RACK_GRID_WIDTH, y * RACK_GRID_HEIGHT)); } } diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 91630e37..28d6768f 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -473,14 +473,14 @@ void Engine::removeModule(Module *module) { paramHandle->module = NULL; } // Update adjacent modules - for (Module *module : internal->modules) { - if (module->leftModule == module) { - module->leftModuleId = -1; - module->leftModule = NULL; + for (Module *m : internal->modules) { + if (m->leftModule == module) { + m->leftModuleId = -1; + m->leftModule = NULL; } - if (module->rightModule == module) { - module->rightModuleId = -1; - module->rightModule = NULL; + if (m->rightModule == module) { + m->rightModuleId = -1; + m->rightModule = NULL; } } // Check that the module actually exists diff --git a/src/patch.cpp b/src/patch.cpp index 8e045651..1fdcbcd5 100644 --- a/src/patch.cpp +++ b/src/patch.cpp @@ -60,7 +60,7 @@ void PatchManager::init(std::string path) { void PatchManager::reset() { APP->history->clear(); APP->scene->rack->clear(); - APP->scene->rackScroll->offset = math::Vec(0, 0); + APP->scene->rackScroll->reset(); // Fails silently if file does not exist load(asset::user("template.vcv")); legacy = 0; @@ -169,7 +169,7 @@ bool PatchManager::load(std::string path) { APP->history->clear(); APP->scene->rack->clear(); - APP->scene->rackScroll->offset = math::Vec(0, 0); + APP->scene->rackScroll->reset(); fromJson(rootJ); return true; } diff --git a/src/ui/ScrollWidget.cpp b/src/ui/ScrollWidget.cpp index 58a6f2a0..1cbdecf8 100644 --- a/src/ui/ScrollWidget.cpp +++ b/src/ui/ScrollWidget.cpp @@ -37,22 +37,17 @@ void ScrollWidget::step() { Widget::step(); // Clamp scroll offset - math::Vec containerCorner = container->getChildrenBoundingBox().getBottomRight(); - math::Rect containerBox = math::Rect(math::Vec(0, 0), containerCorner.minus(box.size)); - offset = offset.clamp(containerBox); - // Lock offset to top/left if no scrollbar will display - if (containerBox.size.x < 0.0) - offset.x = 0.0; - if (containerBox.size.y < 0.0) - offset.y = 0.0; - - // Update the container's positions from the offset + math::Rect containerBox = container->getChildrenBoundingBox(); + math::Rect offsetBounds = containerBox; + offsetBounds.size = offsetBounds.size.minus(box.size); + offset = offset.clamp(offsetBounds); + + // Update the container's position from the offset container->box.pos = offset.neg().round(); // Update scrollbar offsets and sizes - math::Vec viewportSize = container->getChildrenBoundingBox().getBottomRight(); - math::Vec scrollbarOffset = offset.div(viewportSize.minus(box.size)); - math::Vec scrollbarSize = box.size.div(viewportSize); + math::Vec scrollbarOffset = offset.minus(containerBox.pos).div(offsetBounds.size); + math::Vec scrollbarSize = box.size.div(containerBox.size); horizontalScrollBar->visible = (0.0 < scrollbarSize.x && scrollbarSize.x < 1.0); verticalScrollBar->visible = (0.0 < scrollbarSize.y && scrollbarSize.y < 1.0); @@ -61,8 +56,8 @@ void ScrollWidget::step() { horizontalScrollBar->size = scrollbarSize.x; verticalScrollBar->size = scrollbarSize.y; - // Resize scroll bars - math::Vec inner = math::Vec(box.size.x - verticalScrollBar->box.size.x, box.size.y - horizontalScrollBar->box.size.y); + // Reposition and resize scroll bars + math::Vec inner = box.size.minus(math::Vec(verticalScrollBar->box.size.x, horizontalScrollBar->box.size.y)); horizontalScrollBar->box.pos.y = inner.y; verticalScrollBar->box.pos.x = inner.x; horizontalScrollBar->box.size.x = verticalScrollBar->visible ? inner.x : box.size.x;