| @@ -15,19 +15,24 @@ struct FramebufferWidget : Widget { | |||
| bool dirty = true; | |||
| float oversample; | |||
| NVGLUframebuffer *fb = NULL; | |||
| /** Scale relative to the world */ | |||
| math::Vec scale; | |||
| /** Offset in world coordinates */ | |||
| math::Vec offset; | |||
| /** Pixel dimensions of the allocated framebuffer */ | |||
| math::Vec fbSize; | |||
| /** Bounding box in world coordinates of where the framebuffer should be painted | |||
| /** Bounding box in world coordinates of where the framebuffer should be painted. | |||
| Always has integer coordinates so that blitting framebuffers is pixel-perfect. | |||
| */ | |||
| math::Rect fbBox; | |||
| /** Local scale relative to the world scale */ | |||
| /** Framebuffer's scale relative to the world */ | |||
| math::Vec fbScale; | |||
| /** Subpixel offset of fbBox in world coordinates */ | |||
| /** Framebuffer's subpixel offset relative to fbBox in world coordinates */ | |||
| math::Vec fbOffset; | |||
| FramebufferWidget(); | |||
| ~FramebufferWidget(); | |||
| void step() override; | |||
| void draw(const DrawArgs &args) override; | |||
| virtual void drawFramebuffer(); | |||
| int getImageHandle(); | |||
| @@ -84,6 +84,7 @@ struct Widget { | |||
| struct DrawArgs { | |||
| NVGcontext *vg; | |||
| math::Rect clipBox; | |||
| NVGLUframebuffer *fb = NULL; | |||
| }; | |||
| /** Draws the widget to the NanoVG context */ | |||
| @@ -74,8 +74,6 @@ DEPRECATED typedef Svg SVG; | |||
| struct Window { | |||
| GLFWwindow *win = NULL; | |||
| NVGcontext *vg = NULL; | |||
| /** Secondary nanovg context for drawing to framebuffers */ | |||
| NVGcontext *fbVg = NULL; | |||
| /** The scaling ratio */ | |||
| float pixelRatio = 1.f; | |||
| /* The ratio between the framebuffer size and the window size reported by the OS. | |||
| @@ -17,7 +17,7 @@ void LedDisplay::draw(const DrawArgs &args) { | |||
| nvgFill(args.vg); | |||
| nvgScissor(args.vg, RECT_ARGS(args.clipBox)); | |||
| widget::Widget::draw(args); | |||
| Widget::draw(args); | |||
| nvgResetScissor(args.vg); | |||
| } | |||
| @@ -64,11 +64,11 @@ struct BrowserOverlay : widget::OpaqueWidget { | |||
| box = parent->box.zeroPos(); | |||
| // Only step if visible, since there are potentially thousands of descendants that don't need to be stepped. | |||
| if (visible) | |||
| widget::OpaqueWidget::step(); | |||
| OpaqueWidget::step(); | |||
| } | |||
| void onButton(const event::Button &e) override { | |||
| widget::OpaqueWidget::onButton(e); | |||
| OpaqueWidget::onButton(e); | |||
| if (e.getConsumed() != this) | |||
| return; | |||
| @@ -189,6 +189,7 @@ struct ModelBox : widget::OpaqueWidget { | |||
| if (previewWidget && ++visibleFrames >= 60) { | |||
| deletePreview(); | |||
| } | |||
| OpaqueWidget::step(); | |||
| } | |||
| void draw(const DrawArgs &args) override { | |||
| @@ -210,7 +211,7 @@ struct ModelBox : widget::OpaqueWidget { | |||
| nvgFill(args.vg); | |||
| nvgScissor(args.vg, RECT_ARGS(args.clipBox)); | |||
| widget::OpaqueWidget::draw(args); | |||
| OpaqueWidget::draw(args); | |||
| nvgResetScissor(args.vg); | |||
| // Translucent overlay when selected | |||
| @@ -239,7 +240,7 @@ struct BrowserSearchField : ui::TextField { | |||
| void step() override { | |||
| // Steal focus when step is called | |||
| APP->event->setSelected(this); | |||
| ui::TextField::step(); | |||
| TextField::step(); | |||
| } | |||
| void onSelectKey(const event::SelectKey &e) override { | |||
| if (e.action == GLFW_PRESS) { | |||
| @@ -316,7 +317,7 @@ struct BrowserSidebar : widget::Widget { | |||
| tagScroll->box.size.y = (box.size.y - searchField->box.size.y) / 2; | |||
| tagScroll->box.size.x = box.size.x; | |||
| tagList->box.size.x = tagScroll->box.size.x; | |||
| widget::Widget::step(); | |||
| Widget::step(); | |||
| } | |||
| }; | |||
| @@ -363,12 +364,12 @@ struct ModuleBrowser : widget::OpaqueWidget { | |||
| modelMargin->box.size.x = modelScroll->box.size.x; | |||
| modelMargin->box.size.y = modelContainer->getChildrenBoundingBox().size.y + 2 * modelMargin->margin.y; | |||
| widget::OpaqueWidget::step(); | |||
| OpaqueWidget::step(); | |||
| } | |||
| void draw(const DrawArgs &args) override { | |||
| bndMenuBackground(args.vg, 0.0, 0.0, box.size.x, box.size.y, 0); | |||
| widget::Widget::draw(args); | |||
| Widget::draw(args); | |||
| } | |||
| void setSearch(const std::string &search) { | |||
| @@ -388,7 +389,7 @@ struct ModuleBrowser : widget::OpaqueWidget { | |||
| void ModelBox::onButton(const event::Button &e) { | |||
| widget::OpaqueWidget::onButton(e); | |||
| OpaqueWidget::onButton(e); | |||
| if (e.getConsumed() != this) | |||
| return; | |||
| @@ -148,7 +148,7 @@ void ModuleWidget::draw(const DrawArgs &args) { | |||
| nvgGlobalAlpha(args.vg, 0.25); | |||
| } | |||
| widget::Widget::draw(args); | |||
| Widget::draw(args); | |||
| // Power meter | |||
| if (module && settings.cpuMeter) { | |||
| @@ -21,7 +21,7 @@ struct ParamField : ui::TextField { | |||
| void step() override { | |||
| // Keep selected | |||
| APP->event->setSelected(this); | |||
| ui::TextField::step(); | |||
| TextField::step(); | |||
| } | |||
| void setParamWidget(ParamWidget *paramWidget) { | |||
| @@ -54,7 +54,7 @@ struct ParamField : ui::TextField { | |||
| } | |||
| if (!e.getConsumed()) | |||
| ui::TextField::onSelectKey(e); | |||
| TextField::onSelectKey(e); | |||
| } | |||
| }; | |||
| @@ -73,7 +73,7 @@ struct ParamTooltip : ui::Tooltip { | |||
| } | |||
| // Position at bottom-right of parameter | |||
| box.pos = paramWidget->getAbsoluteOffset(paramWidget->box.size).round(); | |||
| ui::Tooltip::step(); | |||
| Tooltip::step(); | |||
| } | |||
| }; | |||
| @@ -82,7 +82,7 @@ struct ParamLabel : ui::MenuLabel { | |||
| ParamWidget *paramWidget; | |||
| void step() override { | |||
| text = paramWidget->paramQuantity->getString(); | |||
| ui::MenuLabel::step(); | |||
| MenuLabel::step(); | |||
| } | |||
| }; | |||
| @@ -138,11 +138,11 @@ void ParamWidget::step() { | |||
| } | |||
| } | |||
| widget::OpaqueWidget::step(); | |||
| OpaqueWidget::step(); | |||
| } | |||
| void ParamWidget::draw(const DrawArgs &args) { | |||
| widget::Widget::draw(args); | |||
| Widget::draw(args); | |||
| // if (paramQuantity) { | |||
| // nvgBeginPath(args.vg); | |||
| @@ -185,7 +185,7 @@ void ParamWidget::onButton(const event::Button &e) { | |||
| } | |||
| if (!e.getConsumed()) | |||
| widget::OpaqueWidget::onButton(e); | |||
| OpaqueWidget::onButton(e); | |||
| } | |||
| void ParamWidget::onDoubleClick(const event::DoubleClick &e) { | |||
| @@ -45,6 +45,8 @@ void PortWidget::step() { | |||
| values[i] = module->inputs[portId].plugLights[i].getBrightness(); | |||
| } | |||
| plugLight->setBrightnesses(values); | |||
| OpaqueWidget::step(); | |||
| } | |||
| void PortWidget::draw(const DrawArgs &args) { | |||
| @@ -54,7 +56,7 @@ void PortWidget::draw(const DrawArgs &args) { | |||
| if (type == OUTPUT ? cw->outputPort : cw->inputPort) | |||
| nvgGlobalAlpha(args.vg, 0.5); | |||
| } | |||
| widget::Widget::draw(args); | |||
| Widget::draw(args); | |||
| } | |||
| void PortWidget::onButton(const event::Button &e) { | |||
| @@ -24,12 +24,12 @@ void RackScrollWidget::step() { | |||
| if (pos.y >= viewport.pos.y + viewport.size.y - margin) | |||
| offset.y += speed; | |||
| } | |||
| ui::ScrollWidget::step(); | |||
| ScrollWidget::step(); | |||
| } | |||
| void RackScrollWidget::draw(const DrawArgs &args) { | |||
| ui::ScrollWidget::draw(args); | |||
| ScrollWidget::draw(args); | |||
| } | |||
| @@ -55,14 +55,14 @@ struct ModuleContainer : widget::Widget { | |||
| nvgRestore(args.vg); | |||
| } | |||
| widget::Widget::draw(args); | |||
| Widget::draw(args); | |||
| } | |||
| }; | |||
| struct CableContainer : widget::TransparentWidget { | |||
| void draw(const DrawArgs &args) override { | |||
| widget::Widget::draw(args); | |||
| Widget::draw(args); | |||
| // Draw cable plugs | |||
| for (widget::Widget *w : children) { | |||
| @@ -114,11 +114,11 @@ void RackWidget::step() { | |||
| rail->box.size = rails->box.size; | |||
| } | |||
| widget::Widget::step(); | |||
| Widget::step(); | |||
| } | |||
| void RackWidget::draw(const DrawArgs &args) { | |||
| widget::Widget::draw(args); | |||
| Widget::draw(args); | |||
| } | |||
| void RackWidget::onHover(const event::Hover &e) { | |||
| @@ -51,7 +51,7 @@ void Scene::step() { | |||
| .plus(math::Vec(500, 500)) | |||
| .div(zoomWidget->zoom); | |||
| widget::OpaqueWidget::step(); | |||
| OpaqueWidget::step(); | |||
| zoomWidget->box.size = rackWidget->box.size.mult(zoomWidget->zoom); | |||
| @@ -88,7 +88,7 @@ void Scene::step() { | |||
| } | |||
| void Scene::draw(const DrawArgs &args) { | |||
| widget::OpaqueWidget::draw(args); | |||
| OpaqueWidget::draw(args); | |||
| } | |||
| void Scene::onHoverKey(const event::HoverKey &e) { | |||
| @@ -149,7 +149,7 @@ void Scene::onHoverKey(const event::HoverKey &e) { | |||
| } | |||
| if (!e.getConsumed()) | |||
| widget::OpaqueWidget::onHoverKey(e); | |||
| OpaqueWidget::onHoverKey(e); | |||
| } | |||
| void Scene::onPathDrop(const event::PathDrop &e) { | |||
| @@ -162,7 +162,7 @@ void Scene::onPathDrop(const event::PathDrop &e) { | |||
| } | |||
| if (!e.getConsumed()) | |||
| widget::OpaqueWidget::onPathDrop(e); | |||
| OpaqueWidget::onPathDrop(e); | |||
| } | |||
| void Scene::runCheckVersion() { | |||
| @@ -20,7 +20,7 @@ void SvgPanel::step() { | |||
| // Small details draw poorly at low DPI, so oversample when drawing to the framebuffer | |||
| oversample = 2.0; | |||
| } | |||
| widget::FramebufferWidget::step(); | |||
| FramebufferWidget::step(); | |||
| } | |||
| void SvgPanel::setBackground(std::shared_ptr<Svg> svg) { | |||
| @@ -25,7 +25,7 @@ namespace app { | |||
| struct MenuButton : ui::Button { | |||
| void step() override { | |||
| box.size.x = bndLabelWidth(APP->window->vg, -1, text.c_str()) + 1.0; | |||
| widget::Widget::step(); | |||
| Widget::step(); | |||
| } | |||
| void draw(const DrawArgs &args) override { | |||
| bndMenuItem(args.vg, 0.0, 0.0, box.size.x, box.size.y, state, -1, text.c_str()); | |||
| @@ -680,7 +680,7 @@ void Toolbar::draw(const DrawArgs &args) { | |||
| bndMenuBackground(args.vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_ALL); | |||
| bndBevel(args.vg, 0.0, 0.0, box.size.x, box.size.y); | |||
| widget::Widget::draw(args); | |||
| Widget::draw(args); | |||
| } | |||
| @@ -6,7 +6,7 @@ namespace ui { | |||
| void List::step() { | |||
| widget::Widget::step(); | |||
| Widget::step(); | |||
| // Set positions of children | |||
| box.size.y = 0.0; | |||
| @@ -7,7 +7,7 @@ namespace ui { | |||
| void MarginLayout::step() { | |||
| widget::Widget::step(); | |||
| Widget::step(); | |||
| math::Rect childBox = box.zeroPos().grow(margin.neg()); | |||
| for (Widget *child : children) { | |||
| @@ -27,7 +27,7 @@ void Menu::setChildMenu(Menu *menu) { | |||
| } | |||
| void Menu::step() { | |||
| widget::Widget::step(); | |||
| Widget::step(); | |||
| // Set positions of children | |||
| box.size = math::Vec(0, 0); | |||
| @@ -51,7 +51,7 @@ void Menu::step() { | |||
| void Menu::draw(const DrawArgs &args) { | |||
| bndMenuBackground(args.vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_NONE); | |||
| widget::Widget::draw(args); | |||
| Widget::draw(args); | |||
| } | |||
| void Menu::onHoverScroll(const event::HoverScroll &e) { | |||
| @@ -34,7 +34,7 @@ void MenuItem::step() { | |||
| // HACK use APP->window->vg from the window. | |||
| // All this does is inspect the font, so it shouldn't modify APP->window->vg and should work when called from a widget::FramebufferWidget for example. | |||
| box.size.x = bndLabelWidth(APP->window->vg, -1, text.c_str()) + bndLabelWidth(APP->window->vg, -1, rightText.c_str()) + rightPadding; | |||
| widget::Widget::step(); | |||
| Widget::step(); | |||
| } | |||
| void MenuItem::onEnter(const event::Enter &e) { | |||
| @@ -15,7 +15,7 @@ void MenuLabel::step() { | |||
| const float rightPadding = 10.0; | |||
| // HACK use APP->window->vg from the window. | |||
| box.size.x = bndLabelWidth(APP->window->vg, -1, text.c_str()) + rightPadding; | |||
| widget::Widget::step(); | |||
| Widget::step(); | |||
| } | |||
| @@ -14,7 +14,7 @@ void MenuOverlay::step() { | |||
| child->box = child->box.nudge(box.zeroPos()); | |||
| } | |||
| widget::Widget::step(); | |||
| Widget::step(); | |||
| } | |||
| void MenuOverlay::onButton(const event::Button &e) { | |||
| @@ -29,12 +29,12 @@ void ScrollWidget::scrollTo(math::Rect r) { | |||
| void ScrollWidget::draw(const DrawArgs &args) { | |||
| nvgScissor(args.vg, RECT_ARGS(args.clipBox)); | |||
| widget::Widget::draw(args); | |||
| Widget::draw(args); | |||
| nvgResetScissor(args.vg); | |||
| } | |||
| void ScrollWidget::step() { | |||
| widget::Widget::step(); | |||
| Widget::step(); | |||
| // Clamp scroll offset | |||
| math::Vec containerCorner = container->getChildrenBoundingBox().getBottomRight(); | |||
| @@ -11,7 +11,7 @@ namespace ui { | |||
| void SequentialLayout::step() { | |||
| widget::Widget::step(); | |||
| Widget::step(); | |||
| // Sort widgets into rows (or columns if vertical) | |||
| std::vector<std::vector<widget::Widget*>> rows; | |||
| @@ -11,13 +11,13 @@ void Tooltip::step() { | |||
| // Wrap size to contents | |||
| box.size.x = bndLabelWidth(APP->window->vg, -1, text.c_str()) + 10.0; | |||
| box.size.y = bndLabelHeight(APP->window->vg, -1, text.c_str(), INFINITY); | |||
| widget::Widget::step(); | |||
| Widget::step(); | |||
| } | |||
| void Tooltip::draw(const DrawArgs &args) { | |||
| bndTooltipBackground(args.vg, 0.0, 0.0, box.size.x, box.size.y); | |||
| bndMenuLabel(args.vg, 0.0, 0.0, box.size.x, box.size.y, -1, text.c_str()); | |||
| widget::Widget::draw(args); | |||
| Widget::draw(args); | |||
| } | |||
| @@ -15,31 +15,18 @@ FramebufferWidget::~FramebufferWidget() { | |||
| nvgluDeleteFramebuffer(fb); | |||
| } | |||
| void FramebufferWidget::draw(const DrawArgs &args) { | |||
| // Bypass framebuffer rendering if we're already drawing in a framebuffer | |||
| // In other words, disallow nested framebuffers. They look bad. | |||
| if (args.vg == APP->window->fbVg) { | |||
| Widget::draw(args); | |||
| return; | |||
| } | |||
| // Get world transform | |||
| float xform[6]; | |||
| nvgCurrentTransform(args.vg, xform); | |||
| // Skew and rotate is not supported | |||
| assert(math::isNear(xform[1], 0.f)); | |||
| assert(math::isNear(xform[2], 0.f)); | |||
| // Extract scale and offset from world transform | |||
| math::Vec scale = math::Vec(xform[0], xform[3]); | |||
| math::Vec offset = math::Vec(xform[4], xform[5]); | |||
| math::Vec offsetI = offset.floor(); | |||
| void FramebufferWidget::step() { | |||
| Widget::step(); | |||
| // Render to framebuffer | |||
| if (dirty) { | |||
| // Render to framebuffer if dirty. | |||
| // Also check that scale has been set by `draw()` yet. | |||
| if (dirty && !scale.isZero()) { | |||
| // In case we fail drawing the framebuffer, don't try again the next frame, so reset `dirty` here. | |||
| dirty = false; | |||
| fbScale = scale; | |||
| // World coordinates, in range [0, 1) | |||
| // Get subpixel offset in range [0, 1) | |||
| math::Vec offsetI = offset.floor(); | |||
| fbOffset = offset.minus(offsetI); | |||
| math::Rect localBox; | |||
| @@ -50,15 +37,16 @@ void FramebufferWidget::draw(const DrawArgs &args) { | |||
| localBox = getChildrenBoundingBox(); | |||
| } | |||
| // DEBUG("%g %g %g %g, %g %g, %g %g", RECT_ARGS(localBox), VEC_ARGS(fbOffset), VEC_ARGS(scale)); | |||
| // DEBUG("%g %g %g %g, %g %g, %g %g", RECT_ARGS(localBox), VEC_ARGS(fbOffset), VEC_ARGS(fbScale)); | |||
| // Transform to world coordinates, then expand to nearest integer coordinates | |||
| math::Vec min = localBox.getTopLeft().mult(scale).plus(fbOffset).floor(); | |||
| math::Vec max = localBox.getBottomRight().mult(scale).plus(fbOffset).ceil(); | |||
| math::Vec min = localBox.getTopLeft().mult(fbScale).plus(fbOffset).floor(); | |||
| math::Vec max = localBox.getBottomRight().mult(fbScale).plus(fbOffset).ceil(); | |||
| fbBox = math::Rect::fromMinMax(min, max); | |||
| // DEBUG("%g %g %g %g", RECT_ARGS(fbBox)); | |||
| math::Vec newFbSize = fbBox.size.mult(APP->window->pixelRatio * oversample); | |||
| // Create framebuffer if a new size is needed | |||
| if (!fb || !newFbSize.isEqual(fbSize)) { | |||
| fbSize = newFbSize; | |||
| // Delete old framebuffer | |||
| @@ -66,7 +54,7 @@ void FramebufferWidget::draw(const DrawArgs &args) { | |||
| nvgluDeleteFramebuffer(fb); | |||
| // Create a framebuffer from the main nanovg context. We will draw to this in the secondary nanovg context. | |||
| if (fbSize.isFinite() && !fbSize.isZero()) | |||
| fb = nvgluCreateFramebuffer(args.vg, fbSize.x, fbSize.y, 0); | |||
| fb = nvgluCreateFramebuffer(APP->window->vg, fbSize.x, fbSize.y, 0); | |||
| } | |||
| if (!fb) | |||
| @@ -76,6 +64,25 @@ void FramebufferWidget::draw(const DrawArgs &args) { | |||
| drawFramebuffer(); | |||
| nvgluBindFramebuffer(NULL); | |||
| } | |||
| } | |||
| void FramebufferWidget::draw(const DrawArgs &args) { | |||
| // Draw directly if already drawing in a framebuffer | |||
| if (args.fb) { | |||
| Widget::draw(args); | |||
| return; | |||
| } | |||
| // Get world transform | |||
| float xform[6]; | |||
| nvgCurrentTransform(args.vg, xform); | |||
| // Skew and rotate is not supported | |||
| assert(math::isNear(xform[1], 0.f)); | |||
| assert(math::isNear(xform[2], 0.f)); | |||
| // Extract scale and offset from world transform | |||
| scale = math::Vec(xform[0], xform[3]); | |||
| offset = math::Vec(xform[4], xform[5]); | |||
| math::Vec offsetI = offset.floor(); | |||
| if (!fb) | |||
| return; | |||
| @@ -106,7 +113,7 @@ void FramebufferWidget::draw(const DrawArgs &args) { | |||
| } | |||
| void FramebufferWidget::drawFramebuffer() { | |||
| NVGcontext *vg = APP->window->fbVg; | |||
| NVGcontext *vg = APP->window->vg; | |||
| float pixelRatio = fbSize.x / fbBox.size.x; | |||
| nvgBeginFrame(vg, fbBox.size.x, fbBox.size.y, pixelRatio); | |||
| @@ -119,6 +126,7 @@ void FramebufferWidget::drawFramebuffer() { | |||
| DrawArgs args; | |||
| args.vg = vg; | |||
| args.clipBox = box.zeroPos(); | |||
| args.fb = fb; | |||
| Widget::draw(args); | |||
| glViewport(0.0, 0.0, fbSize.x, fbSize.y); | |||
| @@ -275,15 +275,6 @@ Window::Window() { | |||
| osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, "Could not initialize NanoVG. Does your graphics card support OpenGL 2.0 or greater? If so, make sure you have the latest graphics drivers installed."); | |||
| exit(1); | |||
| } | |||
| #if defined NANOVG_GL2 | |||
| fbVg = nvgCreateGL2(nvgFlags); | |||
| #elif defined NANOVG_GL3 | |||
| fbVg = nvgCreateGL3(nvgFlags); | |||
| #elif defined NANOVG_GLES2 | |||
| fbVg = nvgCreateGLES2(nvgFlags); | |||
| #endif | |||
| assert(fbVg); | |||
| } | |||
| Window::~Window() { | |||
| @@ -308,14 +299,6 @@ Window::~Window() { | |||
| nvgDeleteGLES2(vg); | |||
| #endif | |||
| #if defined NANOVG_GL2 | |||
| nvgDeleteGL2(fbVg); | |||
| #elif defined NANOVG_GL3 | |||
| nvgDeleteGL3(fbVg); | |||
| #elif defined NANOVG_GLES2 | |||
| nvgDeleteGLES2(fbVg); | |||
| #endif | |||
| glfwDestroyWindow(win); | |||
| delete internal; | |||
| } | |||