@@ -141,6 +141,7 @@ struct RackWidget : OpaqueWidget { | |||||
Widget *onMouseMove(Vec pos, Vec mouseRel) override; | Widget *onMouseMove(Vec pos, Vec mouseRel) override; | ||||
void onMouseDownOpaque(int button) override; | void onMouseDownOpaque(int button) override; | ||||
void onZoom() override; | |||||
}; | }; | ||||
struct RackRail : TransparentWidget { | struct RackRail : TransparentWidget { | ||||
@@ -107,18 +107,6 @@ struct Davies1900hLargeRedKnob : Davies1900hKnob { | |||||
} | } | ||||
}; | }; | ||||
struct Davies1900hSmallBlackKnob : Davies1900hKnob { | |||||
Davies1900hSmallBlackKnob() { | |||||
setSVG(SVG::load(assetGlobal("res/ComponentLibrary/Davies1900hSmallBlack.svg"))); | |||||
} | |||||
}; | |||||
struct Davies1900hSmallBlackSnapKnob : Davies1900hSmallBlackKnob { | |||||
Davies1900hSmallBlackSnapKnob() { | |||||
snap = true; | |||||
} | |||||
}; | |||||
struct Rogan : SVGKnob { | struct Rogan : SVGKnob { | ||||
Rogan() { | Rogan() { | ||||
@@ -197,9 +197,15 @@ struct Vec { | |||||
Vec ceil() { | Vec ceil() { | ||||
return Vec(ceilf(x), ceilf(y)); | return Vec(ceilf(x), ceilf(y)); | ||||
} | } | ||||
bool isEqual(Vec b) { | |||||
return x == b.x && y == b.y; | |||||
} | |||||
bool isZero() { | bool isZero() { | ||||
return x == 0.0 && y == 0.0; | return x == 0.0 && y == 0.0; | ||||
} | } | ||||
bool isFinite() { | |||||
return isfinite(x) && isfinite(y); | |||||
} | |||||
Vec clamp(Rect bound); | Vec clamp(Rect bound); | ||||
}; | }; | ||||
@@ -229,6 +235,9 @@ struct Rect { | |||||
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); | ||||
} | } | ||||
bool isEqual(Rect r) { | |||||
return pos.isEqual(r.pos) && size.isEqual(r.size); | |||||
} | |||||
Vec getCenter() { | Vec getCenter() { | ||||
return pos.plus(size.mult(0.5)); | return pos.plus(size.mult(0.5)); | ||||
} | } | ||||
@@ -241,8 +250,17 @@ struct Rect { | |||||
Vec getBottomRight() { | Vec getBottomRight() { | ||||
return pos.plus(size); | return pos.plus(size); | ||||
} | } | ||||
/** Clamps the position to fix inside a bounding box */ | |||||
/** Clamps the edges of the rectangle to fit within a bound */ | |||||
Rect clamp(Rect bound) { | Rect clamp(Rect bound) { | ||||
Rect r; | |||||
r.pos.x = clampf(pos.x, bound.pos.x, bound.pos.x + bound.size.x); | |||||
r.pos.y = clampf(pos.y, bound.pos.y, bound.pos.y + bound.size.y); | |||||
r.size.x = clampf(pos.x + size.x, bound.pos.x, bound.pos.x + bound.size.x) - r.pos.x; | |||||
r.size.y = clampf(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 */ | |||||
Rect nudge(Rect bound) { | |||||
Rect r; | Rect r; | ||||
r.size = size; | r.size = size; | ||||
r.pos.x = clampf(pos.x, bound.pos.x, bound.pos.x + bound.size.x - size.x); | r.pos.x = clampf(pos.x, bound.pos.x, bound.pos.x + bound.size.x - size.x); | ||||
@@ -58,6 +58,8 @@ struct Widget { | |||||
Vec getAbsolutePos(); | Vec getAbsolutePos(); | ||||
Rect getChildrenBoundingBox(); | Rect getChildrenBoundingBox(); | ||||
/** Returns a subset of the given Rect bounded by the box of this widget and all ancestors */ | |||||
virtual Rect getViewport(Rect r); | |||||
template <class T> | template <class T> | ||||
T *getAncestorOfType() { | T *getAncestorOfType() { | ||||
@@ -131,6 +133,7 @@ struct Widget { | |||||
virtual void onAction() {} | virtual void onAction() {} | ||||
virtual void onChange() {} | virtual void onChange() {} | ||||
virtual void onZoom(); | |||||
}; | }; | ||||
struct TransformWidget : Widget { | struct TransformWidget : Widget { | ||||
@@ -144,6 +147,17 @@ struct TransformWidget : Widget { | |||||
void draw(NVGcontext *vg) override; | void draw(NVGcontext *vg) override; | ||||
}; | }; | ||||
struct ZoomWidget : Widget { | |||||
float zoom = 1.0; | |||||
Rect getViewport(Rect r) override; | |||||
void setZoom(float zoom); | |||||
void draw(NVGcontext *vg) override; | |||||
Widget *onMouseDown(Vec pos, int button) override; | |||||
Widget *onMouseUp(Vec pos, int button) override; | |||||
Widget *onMouseMove(Vec pos, Vec mouseRel) override; | |||||
Widget *onHoverKey(Vec pos, int key) override; | |||||
Widget *onScroll(Vec pos, Vec scrollRel) override; | |||||
}; | |||||
//////////////////// | //////////////////// | ||||
// Trait widgets | // Trait widgets | ||||
@@ -214,7 +228,7 @@ When `dirty` is true, its children will be re-rendered on the next call to step( | |||||
Events are not passed to the underlying scene. | Events are not passed to the underlying scene. | ||||
*/ | */ | ||||
struct FramebufferWidget : virtual Widget { | struct FramebufferWidget : virtual Widget { | ||||
/** Set this to true to re-render the children to the framebuffer in the next step() override */ | |||||
/** Set this to true to re-render the children to the framebuffer the next time it is drawn */ | |||||
bool dirty = true; | bool dirty = true; | ||||
/** The root object in the framebuffer scene | /** The root object in the framebuffer scene | ||||
The FramebufferWidget owns the pointer | The FramebufferWidget owns the pointer | ||||
@@ -226,6 +240,7 @@ struct FramebufferWidget : virtual Widget { | |||||
~FramebufferWidget(); | ~FramebufferWidget(); | ||||
void draw(NVGcontext *vg) override; | void draw(NVGcontext *vg) override; | ||||
int getImageHandle(); | int getImageHandle(); | ||||
void onZoom() override; | |||||
}; | }; | ||||
struct QuantityWidget : virtual Widget { | struct QuantityWidget : virtual Widget { | ||||
@@ -378,16 +393,6 @@ struct ScrollWidget : OpaqueWidget { | |||||
bool onScrollOpaque(Vec scrollRel) override; | bool onScrollOpaque(Vec scrollRel) override; | ||||
}; | }; | ||||
struct ZoomWidget : Widget { | |||||
float zoom = 1.0; | |||||
void draw(NVGcontext *vg) override; | |||||
Widget *onMouseDown(Vec pos, int button) override; | |||||
Widget *onMouseUp(Vec pos, int button) override; | |||||
Widget *onMouseMove(Vec pos, Vec mouseRel) override; | |||||
Widget *onHoverKey(Vec pos, int key) override; | |||||
Widget *onScroll(Vec pos, Vec scrollRel) override; | |||||
}; | |||||
struct TextField : OpaqueWidget { | struct TextField : OpaqueWidget { | ||||
std::string text; | std::string text; | ||||
std::string placeholder; | std::string placeholder; | ||||
@@ -1,108 +0,0 @@ | |||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||||
<!-- Created with Inkscape (http://www.inkscape.org/) --> | |||||
<svg | |||||
xmlns:dc="http://purl.org/dc/elements/1.1/" | |||||
xmlns:cc="http://creativecommons.org/ns#" | |||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | |||||
xmlns:svg="http://www.w3.org/2000/svg" | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | |||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |||||
width="7.0462589mm" | |||||
height="7.1788893mm" | |||||
viewBox="0 0 7.0462589 7.1788893" | |||||
version="1.1" | |||||
id="svg6414" | |||||
inkscape:version="0.92.1 r" | |||||
sodipodi:docname="Davies1900hSmallBlack.svg"> | |||||
<defs | |||||
id="defs6408"> | |||||
<clipPath | |||||
id="clip82"> | |||||
<path | |||||
d="m 979.55859,272.09375 h 26.63281 v 27.13281 h -26.63281 z m 0,0" | |||||
id="path22604" | |||||
inkscape:connector-curvature="0" /> | |||||
</clipPath> | |||||
<clipPath | |||||
id="clip83"> | |||||
<path | |||||
d="m 992,272.09375 h 2 V 287 h -2 z m 0,0" | |||||
id="path22607" | |||||
inkscape:connector-curvature="0" /> | |||||
</clipPath> | |||||
</defs> | |||||
<sodipodi:namedview | |||||
id="base" | |||||
pagecolor="#ffffff" | |||||
bordercolor="#666666" | |||||
borderopacity="1.0" | |||||
inkscape:pageopacity="0.0" | |||||
inkscape:pageshadow="2" | |||||
inkscape:zoom="0.98994949" | |||||
inkscape:cx="101.21858" | |||||
inkscape:cy="-36.897917" | |||||
inkscape:document-units="mm" | |||||
inkscape:current-layer="layer1" | |||||
showgrid="false" | |||||
fit-margin-top="0" | |||||
fit-margin-left="0" | |||||
fit-margin-right="0" | |||||
fit-margin-bottom="0" | |||||
inkscape:window-width="1600" | |||||
inkscape:window-height="882" | |||||
inkscape:window-x="0" | |||||
inkscape:window-y="18" | |||||
inkscape:window-maximized="0" /> | |||||
<metadata | |||||
id="metadata6411"> | |||||
<rdf:RDF> | |||||
<cc:Work | |||||
rdf:about=""> | |||||
<dc:format>image/svg+xml</dc:format> | |||||
<dc:type | |||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | |||||
<dc:title></dc:title> | |||||
</cc:Work> | |||||
</rdf:RDF> | |||||
</metadata> | |||||
<g | |||||
inkscape:label="Layer 1" | |||||
inkscape:groupmode="layer" | |||||
id="layer1" | |||||
transform="translate(-36.542344,-107.44627)"> | |||||
<g | |||||
id="g6344" | |||||
transform="matrix(0.35277777,0,0,-0.35277777,-474.41126,963.93894)"> | |||||
<g | |||||
style="clip-rule:nonzero" | |||||
id="g28551" | |||||
clip-path="url(#clip82)" | |||||
transform="matrix(0.75000002,0,0,-0.75000002,713.70253,2631.9236)"> | |||||
<path | |||||
inkscape:connector-curvature="0" | |||||
id="path28547" | |||||
d="m 1005.8555,288.88281 c -1.6407,7.16406 -8.78128,11.64844 -15.94925,10.00781 -7.16797,-1.64062 -11.64844,-8.78125 -10.00781,-15.94921 1.64062,-7.16797 8.78125,-11.64844 15.94922,-10.00782 7.16794,1.64063 11.64454,8.78125 10.00784,15.94922" | |||||
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" /> | |||||
<path | |||||
inkscape:connector-curvature="0" | |||||
id="path28549" | |||||
d="m 1005.8555,288.88281 c -1.6407,7.16406 -8.78128,11.64844 -15.94925,10.00781 -7.16797,-1.64062 -11.64844,-8.78125 -10.00781,-15.94921 1.64062,-7.16797 8.78125,-11.64844 15.94922,-10.00782 7.16794,1.64063 11.64454,8.78125 10.00784,15.94922" | |||||
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" /> | |||||
</g> | |||||
<g | |||||
style="clip-rule:nonzero" | |||||
id="g28555" | |||||
clip-path="url(#clip83)" | |||||
transform="matrix(0.75000002,0,0,-0.75000002,713.70253,2631.9236)"> | |||||
<path | |||||
inkscape:connector-curvature="0" | |||||
id="path28553" | |||||
transform="translate(992.87612,272.59527)" | |||||
d="M -0.0011146,-0.0015175 V 13.314889" | |||||
style="fill:none;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" /> | |||||
</g> | |||||
</g> | |||||
</g> | |||||
</svg> |
@@ -63,7 +63,7 @@ void RackScene::step() { | |||||
.div(zoomWidget->zoom); | .div(zoomWidget->zoom); | ||||
// Set zoom from the toolbar's zoom slider | // Set zoom from the toolbar's zoom slider | ||||
zoomWidget->zoom = gToolbar->zoomSlider->value / 100.0; | |||||
zoomWidget->setZoom(gToolbar->zoomSlider->value / 100.0); | |||||
Scene::step(); | Scene::step(); | ||||
@@ -16,10 +16,13 @@ namespace rack { | |||||
RackWidget::RackWidget() { | RackWidget::RackWidget() { | ||||
rails = new FramebufferWidget(); | rails = new FramebufferWidget(); | ||||
RackRail *rail = new RackRail(); | |||||
rail->box.size = Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT); | |||||
rails->addChild(rail); | |||||
rails->box.size = rail->box.size; | |||||
rails->box.size = Vec(); | |||||
{ | |||||
RackRail *rail = new RackRail(); | |||||
rail->box.size = Vec(); | |||||
rails->addChild(rail); | |||||
} | |||||
addChild(rails); | |||||
moduleContainer = new Widget(); | moduleContainer = new Widget(); | ||||
addChild(moduleContainer); | addChild(moduleContainer); | ||||
@@ -29,7 +32,6 @@ RackWidget::RackWidget() { | |||||
} | } | ||||
RackWidget::~RackWidget() { | RackWidget::~RackWidget() { | ||||
delete rails; | |||||
} | } | ||||
void RackWidget::clear() { | void RackWidget::clear() { | ||||
@@ -333,13 +335,29 @@ bool RackWidget::requestModuleBoxNearest(ModuleWidget *m, Rect box) { | |||||
} | } | ||||
void RackWidget::step() { | void RackWidget::step() { | ||||
rails->step(); | |||||
// Expand size to fit modules | // Expand size to fit modules | ||||
Vec moduleSize = moduleContainer->getChildrenBoundingBox().getBottomRight(); | Vec moduleSize = moduleContainer->getChildrenBoundingBox().getBottomRight(); | ||||
// We assume that the size is reset by a parent before calling step(). Otherwise it will grow unbounded. | // We assume that the size is reset by a parent before calling step(). Otherwise it will grow unbounded. | ||||
box.size = box.size.max(moduleSize); | box.size = box.size.max(moduleSize); | ||||
// Adjust size and position of rails | |||||
Widget *rail = rails->children.front(); | |||||
Rect bound = getViewport(Rect(Vec(), box.size)); | |||||
if (!rails->box.contains(bound)) { | |||||
// Add a margin around the otherwise tight bound, so that scrolling slightly will not require a re-render of rails. | |||||
Vec margin = Vec(100, 100); | |||||
bound.pos = bound.pos.minus(margin); | |||||
bound.size = bound.size.plus(margin.mult(2)); | |||||
rails->box = bound; | |||||
// Compute offset of rail within rails framebuffer | |||||
Vec grid = Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT); | |||||
Vec gridPos = bound.pos.div(grid).floor().mult(grid); | |||||
bound.pos = gridPos.minus(bound.pos); | |||||
bound.size = bound.size.minus(bound.pos); | |||||
rail->box = bound; | |||||
rails->dirty = true; | |||||
} | |||||
// Autosave every 15 seconds | // Autosave every 15 seconds | ||||
if (gGuiFrame % (60*15) == 0) { | if (gGuiFrame % (60*15) == 0) { | ||||
savePatch(assetLocal("autosave.vcv")); | savePatch(assetLocal("autosave.vcv")); | ||||
@@ -350,13 +368,6 @@ void RackWidget::step() { | |||||
} | } | ||||
void RackWidget::draw(NVGcontext *vg) { | void RackWidget::draw(NVGcontext *vg) { | ||||
// Draw rails | |||||
nvgBeginPath(vg); | |||||
nvgRect(vg, 0.0, 0.0, box.size.x, box.size.y); | |||||
NVGpaint paint = nvgImagePattern(vg, rails->box.pos.x, rails->box.pos.y, rails->box.size.x, rails->box.size.y, 0.0, rails->getImageHandle(), 1.0); | |||||
nvgFillPaint(vg, paint); | |||||
nvgFill(vg); | |||||
Widget::draw(vg); | Widget::draw(vg); | ||||
} | } | ||||
@@ -513,5 +524,10 @@ void RackWidget::onMouseDownOpaque(int button) { | |||||
} | } | ||||
} | } | ||||
void RackWidget::onZoom() { | |||||
rails->box.size = Vec(); | |||||
Widget::onZoom(); | |||||
} | |||||
} // namespace rack | } // namespace rack |
@@ -153,7 +153,7 @@ Toolbar::Toolbar() { | |||||
zoomSlider->box.size.x = 150; | zoomSlider->box.size.x = 150; | ||||
zoomSlider->label = "Zoom"; | zoomSlider->label = "Zoom"; | ||||
zoomSlider->unit = "%"; | zoomSlider->unit = "%"; | ||||
zoomSlider->setLimits(25.0, 200.0); | |||||
zoomSlider->setLimits(33.33, 200.0); | |||||
zoomSlider->setDefaultValue(100.0); | zoomSlider->setDefaultValue(100.0); | ||||
addChild(zoomSlider); | addChild(zoomSlider); | ||||
xPos += zoomSlider->box.size.x; | xPos += zoomSlider->box.size.x; | ||||
@@ -18,7 +18,6 @@ static const float oversample = 2.0; | |||||
struct FramebufferWidget::Internal { | struct FramebufferWidget::Internal { | ||||
NVGLUframebuffer *fb = NULL; | NVGLUframebuffer *fb = NULL; | ||||
Rect box; | Rect box; | ||||
Vec lastS; | |||||
~Internal() { | ~Internal() { | ||||
setFramebuffer(NULL); | setFramebuffer(NULL); | ||||
@@ -47,20 +46,16 @@ void FramebufferWidget::draw(NVGcontext *vg) { | |||||
// Get world transform | // Get world transform | ||||
float xform[6]; | float xform[6]; | ||||
nvgCurrentTransform(vg, xform); | nvgCurrentTransform(vg, xform); | ||||
// Skew is not supported | |||||
// Skew and rotate is not supported | |||||
assert(fabsf(xform[1]) < 1e-6); | assert(fabsf(xform[1]) < 1e-6); | ||||
assert(fabsf(xform[2]) < 1e-6); | assert(fabsf(xform[2]) < 1e-6); | ||||
Vec s = Vec(xform[0], xform[3]); | Vec s = Vec(xform[0], xform[3]); | ||||
Vec b = Vec(xform[4], xform[5]); | Vec b = Vec(xform[4], xform[5]); | ||||
// Check if scale has changed | |||||
if (s.x != internal->lastS.x || s.y != internal->lastS.y) { | |||||
dirty = true; | |||||
} | |||||
internal->lastS = s; | |||||
// Render to framebuffer | // Render to framebuffer | ||||
if (dirty) { | if (dirty) { | ||||
dirty = false; | |||||
internal->box.pos = Vec(0, 0); | internal->box.pos = Vec(0, 0); | ||||
internal->box.size = box.size.mult(s).ceil().plus(Vec(1, 1)); | internal->box.size = box.size.mult(s).ceil().plus(Vec(1, 1)); | ||||
Vec fbSize = internal->box.size.mult(gPixelRatio * oversample); | Vec fbSize = internal->box.size.mult(gPixelRatio * oversample); | ||||
@@ -73,7 +68,7 @@ void FramebufferWidget::draw(NVGcontext *vg) { | |||||
// Delete old one first to free up GPU memory | // Delete old one first to free up GPU memory | ||||
internal->setFramebuffer(NULL); | internal->setFramebuffer(NULL); | ||||
// Create a framebuffer from the main nanovg context. We will draw to this in the secondary nanovg context. | // Create a framebuffer from the main nanovg context. We will draw to this in the secondary nanovg context. | ||||
NVGLUframebuffer *fb = nvgluCreateFramebuffer(gVg, fbSize.x, fbSize.y, NVG_IMAGE_REPEATX | NVG_IMAGE_REPEATY); | |||||
NVGLUframebuffer *fb = nvgluCreateFramebuffer(gVg, fbSize.x, fbSize.y, 0); | |||||
if (!fb) | if (!fb) | ||||
return; | return; | ||||
internal->setFramebuffer(fb); | internal->setFramebuffer(fb); | ||||
@@ -94,8 +89,6 @@ void FramebufferWidget::draw(NVGcontext *vg) { | |||||
nvgEndFrame(gFramebufferVg); | nvgEndFrame(gFramebufferVg); | ||||
nvgluBindFramebuffer(NULL); | nvgluBindFramebuffer(NULL); | ||||
dirty = false; | |||||
} | } | ||||
if (!internal->fb) { | if (!internal->fb) { | ||||
@@ -128,5 +121,9 @@ int FramebufferWidget::getImageHandle() { | |||||
return internal->fb->image; | return internal->fb->image; | ||||
} | } | ||||
void FramebufferWidget::onZoom() { | |||||
dirty = true; | |||||
} | |||||
} // namespace rack | } // namespace rack |
@@ -30,7 +30,7 @@ void Menu::setChildMenu(Menu *menu) { | |||||
void Menu::step() { | void Menu::step() { | ||||
// Try to fit into the parent's box | // Try to fit into the parent's box | ||||
if (parent) | if (parent) | ||||
box = box.clamp(Rect(Vec(0, 0), parent->box.size)); | |||||
box = box.nudge(Rect(Vec(0, 0), parent->box.size)); | |||||
Widget::step(); | Widget::step(); | ||||
@@ -40,6 +40,18 @@ Rect Widget::getChildrenBoundingBox() { | |||||
return Rect(topLeft, bottomRight.minus(topLeft)); | return Rect(topLeft, bottomRight.minus(topLeft)); | ||||
} | } | ||||
Rect Widget::getViewport(Rect r) { | |||||
Rect bound; | |||||
if (parent) { | |||||
bound = parent->getViewport(box); | |||||
} | |||||
else { | |||||
bound = box; | |||||
} | |||||
bound.pos = bound.pos.minus(box.pos); | |||||
return r.clamp(bound); | |||||
} | |||||
void Widget::addChild(Widget *widget) { | void Widget::addChild(Widget *widget) { | ||||
assert(!widget->parent); | assert(!widget->parent); | ||||
widget->parent = this; | widget->parent = this; | ||||
@@ -172,4 +184,11 @@ Widget *Widget::onScroll(Vec pos, Vec scrollRel) { | |||||
return NULL; | return NULL; | ||||
} | } | ||||
void Widget::onZoom() { | |||||
for (auto it = children.rbegin(); it != children.rend(); it++) { | |||||
Widget *child = *it; | |||||
child->onZoom(); | |||||
} | |||||
} | |||||
} // namespace rack | } // namespace rack |
@@ -3,6 +3,20 @@ | |||||
namespace rack { | namespace rack { | ||||
Rect ZoomWidget::getViewport(Rect r) { | |||||
r.pos = r.pos.mult(zoom); | |||||
r.size = r.size.mult(zoom); | |||||
r = Widget::getViewport(r); | |||||
r.pos = r.pos.div(zoom); | |||||
r.size = r.size.div(zoom); | |||||
return r; | |||||
} | |||||
void ZoomWidget::setZoom(float zoom) { | |||||
if (zoom != this->zoom) | |||||
onZoom(); | |||||
this->zoom = zoom; | |||||
} | |||||
void ZoomWidget::draw(NVGcontext *vg) { | void ZoomWidget::draw(NVGcontext *vg) { | ||||
nvgScale(vg, zoom, zoom); | nvgScale(vg, zoom, zoom); | ||||