@@ -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; | |||
} | |||