| @@ -7,9 +7,8 @@ import json | |||||
| import xml.etree.ElementTree | 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): | class UserException(Exception): | ||||
| @@ -20,6 +20,7 @@ struct RackScrollWidget : ui::ScrollWidget { | |||||
| void draw(const DrawArgs &args) override; | void draw(const DrawArgs &args) override; | ||||
| void onHover(const widget::HoverEvent &e) override; | void onHover(const widget::HoverEvent &e) override; | ||||
| void onHoverScroll(const widget::HoverScrollEvent &e) override; | void onHoverScroll(const widget::HoverScrollEvent &e) override; | ||||
| void reset(); | |||||
| }; | }; | ||||
| @@ -43,6 +43,7 @@ inline math::Vec mm2px(math::Vec mm) { | |||||
| static const float RACK_GRID_WIDTH = 15; | static const float RACK_GRID_WIDTH = 15; | ||||
| static const float RACK_GRID_HEIGHT = 380; | 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_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 | } // namespace app | ||||
| @@ -15,25 +15,25 @@ namespace math { | |||||
| // basic integer functions | // basic integer functions | ||||
| //////////////////// | //////////////////// | ||||
| /** Returns true if x is odd */ | |||||
| /** Returns true if x is odd. */ | |||||
| inline bool isEven(int x) { | inline bool isEven(int x) { | ||||
| return x % 2 == 0; | return x % 2 == 0; | ||||
| } | } | ||||
| /** Returns true if x is odd */ | |||||
| /** Returns true if x is odd. */ | |||||
| inline bool isOdd(int x) { | inline bool isOdd(int x) { | ||||
| return x % 2 != 0; | 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) { | 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) { | inline int clampSafe(int x, int a, int b) { | ||||
| return clamp(x, std::min(a, b), std::max(a, 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) { | inline int log2(int n) { | ||||
| int i = 0; | int i = 0; | ||||
| while (n >>= 1) { | while (n >>= 1) { | ||||
| @@ -82,7 +81,7 @@ inline int log2(int n) { | |||||
| return i; | return i; | ||||
| } | } | ||||
| /** Returns whether `n` is a power of 2 */ | |||||
| /** Returns whether `n` is a power of 2. */ | |||||
| inline bool isPow2(int n) { | inline bool isPow2(int n) { | ||||
| return n > 0 && (n & (n - 1)) == 0; | return n > 0 && (n & (n - 1)) == 0; | ||||
| } | } | ||||
| @@ -91,22 +90,22 @@ inline bool isPow2(int n) { | |||||
| // basic float functions | // 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) { | 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) { | inline float clampSafe(float x, float a, float b) { | ||||
| return clamp(x, std::fmin(a, b), std::fmax(a, 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) { | inline float sgn(float x) { | ||||
| return x > 0.f ? 1.f : x < 0.f ? -1.f : 0.f; | 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. | /** 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) { | inline float eucMod(float a, float base) { | ||||
| float mod = std::fmod(a, base); | float mod = std::fmod(a, base); | ||||
| return (mod >= 0.f) ? mod : mod + 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) { | inline bool isNear(float a, float b, float epsilon = 1e-6f) { | ||||
| return std::fabs(a - b) <= epsilon; | 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) { | inline float chop(float x, float epsilon = 1e-6f) { | ||||
| return isNear(x, 0.f, epsilon) ? 0.f : x; | 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; | 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. | Assumes that the array at `p` is of length at least floor(x)+1. | ||||
| */ | */ | ||||
| inline float interpolateLinear(const float *p, float x) { | 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); | 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) | i.e. cmultf(&ar, &ai, ar, ai, br, bi) | ||||
| */ | */ | ||||
| inline void complexMult(float *cr, float *ci, float ar, float ai, float br, float bi) { | inline void complexMult(float *cr, float *ci, float ar, float ai, float br, float bi) { | ||||
| @@ -173,7 +173,7 @@ struct Vec { | |||||
| Vec() {} | Vec() {} | ||||
| Vec(float x, float y) : x(x), y(y) {} | Vec(float x, float y) : x(x), y(y) {} | ||||
| /** Negates the vector | |||||
| /** Negates the vector. | |||||
| Equivalent to a reflection across the y=-x line. | Equivalent to a reflection across the y=-x line. | ||||
| */ | */ | ||||
| Vec neg() const { | Vec neg() const { | ||||
| @@ -206,13 +206,13 @@ struct Vec { | |||||
| float square() const { | float square() const { | ||||
| return x * x + y * y; | return x * x + y * y; | ||||
| } | } | ||||
| /** Rotates counterclockwise in radians */ | |||||
| /** Rotates counterclockwise in radians. */ | |||||
| Vec rotate(float angle) { | Vec rotate(float angle) { | ||||
| float sin = std::sin(angle); | float sin = std::sin(angle); | ||||
| float cos = std::cos(angle); | float cos = std::cos(angle); | ||||
| return Vec(x * cos - y * sin, x * sin + y * cos); | 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. | Equivalent to a reflection across the y=x line. | ||||
| */ | */ | ||||
| Vec flip() const { | Vec flip() const { | ||||
| @@ -256,22 +256,23 @@ struct Rect { | |||||
| Rect() {} | Rect() {} | ||||
| Rect(Vec pos, Vec size) : pos(pos), size(size) {} | 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) { | static Rect fromMinMax(Vec a, Vec b) { | ||||
| return Rect(a, b.minus(a)); | 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 { | bool isContaining(Vec v) const { | ||||
| return pos.x <= v.x && v.x < pos.x + size.x | return pos.x <= v.x && v.x < pos.x + size.x | ||||
| && pos.y <= v.y && v.y < pos.y + size.y; | && 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 { | bool isContaining(Rect r) const { | ||||
| return pos.x <= r.pos.x && r.pos.x + r.size.x <= pos.x + size.x | 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; | && 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 { | bool isIntersecting(Rect r) const { | ||||
| return (pos.x + size.x > r.pos.x && r.pos.x + r.size.x > pos.x) | 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); | && (pos.y + size.y > r.pos.y && r.pos.y + r.size.y > pos.y); | ||||
| @@ -300,7 +301,7 @@ struct Rect { | |||||
| Vec getBottomRight() const { | Vec getBottomRight() const { | ||||
| return pos.plus(size); | 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 clamp(Rect bound) const { | ||||
| Rect r; | Rect r; | ||||
| r.pos.x = math::clampSafe(pos.x, bound.pos.x, bound.pos.x + bound.size.x); | 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; | r.size.y = math::clamp(pos.y + size.y, bound.pos.y, bound.pos.y + bound.size.y) - r.pos.y; | ||||
| return r; | 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 nudge(Rect bound) const { | ||||
| Rect r; | Rect r; | ||||
| r.size = size; | 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); | r.pos.y = math::clampSafe(pos.y, bound.pos.y, bound.pos.y + bound.size.y - size.y); | ||||
| return r; | return r; | ||||
| } | } | ||||
| /** Expands this Rect to contain `b` */ | |||||
| /** Expands this Rect to contain `b`. */ | |||||
| Rect expand(Rect b) const { | Rect expand(Rect b) const { | ||||
| Rect r; | Rect r; | ||||
| r.pos.x = std::fmin(pos.x, b.pos.x); | 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; | r.size.y = std::fmax(pos.y + size.y, b.pos.y + b.size.y) - r.pos.y; | ||||
| return r; | return r; | ||||
| } | } | ||||
| /** Returns the intersection of `this` and `b` */ | |||||
| /** Returns the intersection of `this` and `b`. */ | |||||
| Rect intersect(Rect b) const { | Rect intersect(Rect b) const { | ||||
| Rect r; | Rect r; | ||||
| r.pos.x = std::fmax(pos.x, b.pos.x); | 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; | r.size.y = std::fmin(pos.y + size.y, b.pos.y + b.size.y) - r.pos.y; | ||||
| return r; | return r; | ||||
| } | } | ||||
| /** Returns a Rect with its position set to zero */ | |||||
| /** Returns a Rect with its position set to zero. */ | |||||
| Rect zeroPos() const { | Rect zeroPos() const { | ||||
| return Rect(Vec(), size); | return Rect(Vec(), size); | ||||
| } | } | ||||
| /** Expands each corner | |||||
| /** Expands each corner. | |||||
| Use a negative delta to shrink. | Use a negative delta to shrink. | ||||
| */ | */ | ||||
| Rect grow(Vec delta) const { | 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. | /** Expands a Vec and Rect into a comma-separated list. | ||||
| Useful for print debugging. | 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. | Or passing the values to a C function. | ||||
| @@ -47,15 +47,6 @@ void RackRail::draw(const DrawArgs &args) { | |||||
| nvgLineTo(args.vg, box.size.x, railY + RACK_GRID_HEIGHT - 0.5); | nvgLineTo(args.vg, box.size.x, railY + RACK_GRID_HEIGHT - 0.5); | ||||
| nvgStroke(args.vg); | 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); | |||||
| } | |||||
| } | } | ||||
| @@ -14,11 +14,14 @@ RackScrollWidget::RackScrollWidget() { | |||||
| container->addChild(zoomWidget); | container->addChild(zoomWidget); | ||||
| rackWidget = new RackWidget; | rackWidget = new RackWidget; | ||||
| rackWidget->box.size = RACK_OFFSET.mult(2); | |||||
| zoomWidget->addChild(rackWidget); | zoomWidget->addChild(rackWidget); | ||||
| reset(); | |||||
| } | } | ||||
| void RackScrollWidget::step() { | 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) { | if (zoom != zoomWidget->zoom) { | ||||
| // Set offset based on zoomPos | // Set offset based on zoomPos | ||||
| offset = offset.plus(zoomPos).div(zoomWidget->zoom).mult(zoom).minus(zoomPos); | offset = offset.plus(zoomPos).div(zoomWidget->zoom).mult(zoom).minus(zoomPos); | ||||
| @@ -28,14 +31,20 @@ void RackScrollWidget::step() { | |||||
| zoomPos = box.size.div(2); | 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 | // Scroll rack if dragging cable near the edge of the screen | ||||
| math::Vec pos = APP->window->mousePos; | math::Vec pos = APP->window->mousePos; | ||||
| @@ -58,6 +67,7 @@ void RackScrollWidget::step() { | |||||
| void RackScrollWidget::draw(const DrawArgs &args) { | void RackScrollWidget::draw(const DrawArgs &args) { | ||||
| // DEBUG("%f %f %f %f", RECT_ARGS(args.clipBox)); | |||||
| ScrollWidget::draw(args); | 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) | else if ((APP->window->getMods() & WINDOW_MOD_MASK) == GLFW_MOD_SHIFT) | ||||
| arrowSpeed /= 4.0; | 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; | 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; | 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; | 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; | offset.y += arrowSpeed; | ||||
| } | |||||
| } | } | ||||
| ScrollWidget::onHover(e); | ScrollWidget::onHover(e); | ||||
| @@ -105,6 +111,11 @@ void RackScrollWidget::onHoverScroll(const widget::HoverScrollEvent &e) { | |||||
| ScrollWidget::onHoverScroll(e); | ScrollWidget::onHoverScroll(e); | ||||
| } | } | ||||
| void RackScrollWidget::reset() { | |||||
| offset = RACK_OFFSET.mult(zoomWidget->zoom); | |||||
| offset = offset.minus(math::Vec(20, 20)); | |||||
| } | |||||
| } // namespace app | } // namespace app | ||||
| } // namespace rack | } // namespace rack | ||||
| @@ -97,13 +97,7 @@ RackWidget::~RackWidget() { | |||||
| } | } | ||||
| void RackWidget::step() { | 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 | // Adjust size and position of rails | ||||
| widget::Widget *rail = rails->children.front(); | |||||
| math::Rect bound = getViewport(math::Rect(math::Vec(), box.size)); | math::Rect bound = getViewport(math::Rect(math::Vec(), box.size)); | ||||
| if (!rails->box.isContaining(bound)) { | if (!rails->box.isContaining(bound)) { | ||||
| math::Vec cellMargin = math::Vec(20, 1); | 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->box.size = bound.size.plus(cellMargin.mult(RACK_GRID_SIZE).mult(2)); | ||||
| rails->dirty = true; | rails->dirty = true; | ||||
| RackRail *rail = rails->getFirstDescendantOfType<RackRail>(); | |||||
| rail->box.size = rails->box.size; | rail->box.size = rails->box.size; | ||||
| } | } | ||||
| Widget::step(); | |||||
| OpaqueWidget::step(); | |||||
| } | } | ||||
| void RackWidget::draw(const DrawArgs &args) { | void RackWidget::draw(const DrawArgs &args) { | ||||
| Widget::draw(args); | |||||
| OpaqueWidget::draw(args); | |||||
| } | } | ||||
| void RackWidget::onHover(const widget::HoverEvent &e) { | 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) { | void RackWidget::onZoom(const widget::ZoomEvent &e) { | ||||
| rails->box.size = math::Vec(); | |||||
| OpaqueWidget::onZoom(e); | OpaqueWidget::onZoom(e); | ||||
| } | } | ||||
| @@ -189,7 +183,8 @@ json_t *RackWidget::toJson() { | |||||
| // id | // id | ||||
| json_object_set_new(moduleJ, "id", json_integer(moduleWidget->module->id)); | json_object_set_new(moduleJ, "id", json_integer(moduleWidget->module->id)); | ||||
| // pos | // 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_t *posJ = json_pack("[i, i]", (int) pos.x, (int) pos.y); | ||||
| json_object_set_new(moduleJ, "pos", posJ); | json_object_set_new(moduleJ, "pos", posJ); | ||||
| } | } | ||||
| @@ -252,6 +247,7 @@ void RackWidget::fromJson(json_t *rootJ) { | |||||
| else { | else { | ||||
| moduleWidget->box.pos = pos.mult(RACK_GRID_SIZE); | moduleWidget->box.pos = pos.mult(RACK_GRID_SIZE); | ||||
| } | } | ||||
| moduleWidget->box.pos = moduleWidget->box.pos.plus(RACK_OFFSET); | |||||
| addModule(moduleWidget); | addModule(moduleWidget); | ||||
| } | } | ||||
| @@ -387,10 +383,6 @@ void RackWidget::removeModule(ModuleWidget *m) { | |||||
| } | } | ||||
| bool RackWidget::requestModuleBox(ModuleWidget *m, math::Rect requestedBox) { | 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 | // Check intersection with other modules | ||||
| for (widget::Widget *m2 : moduleContainer->children) { | for (widget::Widget *m2 : moduleContainer->children) { | ||||
| // Don't intersect with self | // 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 x0 = std::round(requestedBox.pos.x / RACK_GRID_WIDTH); | ||||
| int y0 = std::round(requestedBox.pos.y / RACK_GRID_HEIGHT); | int y0 = std::round(requestedBox.pos.y / RACK_GRID_HEIGHT); | ||||
| std::vector<math::Vec> positions; | std::vector<math::Vec> 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)); | positions.push_back(math::Vec(x * RACK_GRID_WIDTH, y * RACK_GRID_HEIGHT)); | ||||
| } | } | ||||
| } | } | ||||
| @@ -473,14 +473,14 @@ void Engine::removeModule(Module *module) { | |||||
| paramHandle->module = NULL; | paramHandle->module = NULL; | ||||
| } | } | ||||
| // Update adjacent modules | // 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 | // Check that the module actually exists | ||||
| @@ -60,7 +60,7 @@ void PatchManager::init(std::string path) { | |||||
| void PatchManager::reset() { | void PatchManager::reset() { | ||||
| APP->history->clear(); | APP->history->clear(); | ||||
| APP->scene->rack->clear(); | APP->scene->rack->clear(); | ||||
| APP->scene->rackScroll->offset = math::Vec(0, 0); | |||||
| APP->scene->rackScroll->reset(); | |||||
| // Fails silently if file does not exist | // Fails silently if file does not exist | ||||
| load(asset::user("template.vcv")); | load(asset::user("template.vcv")); | ||||
| legacy = 0; | legacy = 0; | ||||
| @@ -169,7 +169,7 @@ bool PatchManager::load(std::string path) { | |||||
| APP->history->clear(); | APP->history->clear(); | ||||
| APP->scene->rack->clear(); | APP->scene->rack->clear(); | ||||
| APP->scene->rackScroll->offset = math::Vec(0, 0); | |||||
| APP->scene->rackScroll->reset(); | |||||
| fromJson(rootJ); | fromJson(rootJ); | ||||
| return true; | return true; | ||||
| } | } | ||||
| @@ -37,22 +37,17 @@ void ScrollWidget::step() { | |||||
| Widget::step(); | Widget::step(); | ||||
| // Clamp scroll offset | // 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(); | container->box.pos = offset.neg().round(); | ||||
| // Update scrollbar offsets and sizes | // 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); | horizontalScrollBar->visible = (0.0 < scrollbarSize.x && scrollbarSize.x < 1.0); | ||||
| verticalScrollBar->visible = (0.0 < scrollbarSize.y && scrollbarSize.y < 1.0); | verticalScrollBar->visible = (0.0 < scrollbarSize.y && scrollbarSize.y < 1.0); | ||||
| @@ -61,8 +56,8 @@ void ScrollWidget::step() { | |||||
| horizontalScrollBar->size = scrollbarSize.x; | horizontalScrollBar->size = scrollbarSize.x; | ||||
| verticalScrollBar->size = scrollbarSize.y; | 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; | horizontalScrollBar->box.pos.y = inner.y; | ||||
| verticalScrollBar->box.pos.x = inner.x; | verticalScrollBar->box.pos.x = inner.x; | ||||
| horizontalScrollBar->box.size.x = verticalScrollBar->visible ? inner.x : box.size.x; | horizontalScrollBar->box.size.x = verticalScrollBar->visible ? inner.x : box.size.x; | ||||