| @@ -312,6 +312,7 @@ struct SVGScrew : FramebufferWidget { | |||||
| struct Toolbar : OpaqueWidget { | struct Toolbar : OpaqueWidget { | ||||
| Slider *wireOpacitySlider; | Slider *wireOpacitySlider; | ||||
| Slider *wireTensionSlider; | Slider *wireTensionSlider; | ||||
| Slider *zoomSlider; | |||||
| RadioButton *cpuUsageButton; | RadioButton *cpuUsageButton; | ||||
| RadioButton *plugLightButton; | RadioButton *plugLightButton; | ||||
| @@ -17,9 +17,12 @@ namespace rack { | |||||
| extern GLFWwindow *gWindow; | extern GLFWwindow *gWindow; | ||||
| extern NVGcontext *gVg; | extern NVGcontext *gVg; | ||||
| extern NVGcontext *gFramebufferVg; | |||||
| extern std::shared_ptr<Font> gGuiFont; | extern std::shared_ptr<Font> gGuiFont; | ||||
| extern float gPixelRatio; | extern float gPixelRatio; | ||||
| extern bool gAllowCursorLock; | extern bool gAllowCursorLock; | ||||
| extern int gGuiFrame; | |||||
| extern Vec gMousePos; | |||||
| void guiInit(); | void guiInit(); | ||||
| @@ -191,6 +191,12 @@ struct Vec { | |||||
| Vec round() { | Vec round() { | ||||
| return Vec(roundf(x), roundf(y)); | return Vec(roundf(x), roundf(y)); | ||||
| } | } | ||||
| Vec floor() { | |||||
| return Vec(floorf(x), floorf(y)); | |||||
| } | |||||
| Vec ceil() { | |||||
| return Vec(ceilf(x), ceilf(y)); | |||||
| } | |||||
| bool isZero() { | bool isZero() { | ||||
| return x == 0.0 && y == 0.0; | return x == 0.0 && y == 0.0; | ||||
| } | } | ||||
| @@ -216,10 +216,6 @@ 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 in the next step() override */ | ||||
| bool dirty = true; | bool dirty = true; | ||||
| /** A margin in pixels around the children in the framebuffer | |||||
| This prevents cutting the rendered SVG off on the box edges. | |||||
| */ | |||||
| float oversample = 2.0; | |||||
| /** The root object in the framebuffer scene | /** The root object in the framebuffer scene | ||||
| The FramebufferWidget owns the pointer | The FramebufferWidget owns the pointer | ||||
| */ | */ | ||||
| @@ -228,7 +224,6 @@ struct FramebufferWidget : virtual Widget { | |||||
| FramebufferWidget(); | FramebufferWidget(); | ||||
| ~FramebufferWidget(); | ~FramebufferWidget(); | ||||
| void step() override; | |||||
| void draw(NVGcontext *vg) override; | void draw(NVGcontext *vg) override; | ||||
| int getImageHandle(); | int getImageHandle(); | ||||
| }; | }; | ||||
| @@ -354,6 +349,7 @@ struct Slider : OpaqueWidget, QuantityWidget { | |||||
| void onDragStart() override; | void onDragStart() override; | ||||
| void onDragMove(Vec mouseRel) override; | void onDragMove(Vec mouseRel) override; | ||||
| void onDragEnd() override; | void onDragEnd() override; | ||||
| void onMouseDownOpaque(int button) override; | |||||
| }; | }; | ||||
| /** Parent must be a ScrollWidget */ | /** Parent must be a ScrollWidget */ | ||||
| @@ -439,12 +435,10 @@ struct Scene : OpaqueWidget { | |||||
| // globals | // globals | ||||
| //////////////////// | //////////////////// | ||||
| extern Vec gMousePos; | |||||
| extern Widget *gHoveredWidget; | extern Widget *gHoveredWidget; | ||||
| extern Widget *gDraggedWidget; | extern Widget *gDraggedWidget; | ||||
| extern Widget *gDragHoveredWidget; | extern Widget *gDragHoveredWidget; | ||||
| extern Widget *gFocusedWidget; | extern Widget *gFocusedWidget; | ||||
| extern int gGuiFrame; | |||||
| extern Scene *gScene; | extern Scene *gScene; | ||||
| @@ -31,7 +31,6 @@ RackScene::RackScene() { | |||||
| scrollWidget = new RackScrollWidget(); | scrollWidget = new RackScrollWidget(); | ||||
| { | { | ||||
| zoomWidget = new ZoomWidget(); | zoomWidget = new ZoomWidget(); | ||||
| zoomWidget->zoom = 1.0; | |||||
| { | { | ||||
| assert(!gRackWidget); | assert(!gRackWidget); | ||||
| gRackWidget = new RackWidget(); | gRackWidget = new RackWidget(); | ||||
| @@ -63,6 +62,9 @@ void RackScene::step() { | |||||
| .plus(Vec(500, 500)) | .plus(Vec(500, 500)) | ||||
| .div(zoomWidget->zoom); | .div(zoomWidget->zoom); | ||||
| // Set zoom from the toolbar's zoom slider | |||||
| zoomWidget->zoom = gToolbar->zoomSlider->value / 100.0; | |||||
| Scene::step(); | Scene::step(); | ||||
| zoomWidget->box.size = gRackWidget->box.size.mult(zoomWidget->zoom); | zoomWidget->box.size = gRackWidget->box.size.mult(zoomWidget->zoom); | ||||
| @@ -1,4 +1,5 @@ | |||||
| #include "app.hpp" | #include "app.hpp" | ||||
| #include "gui.hpp" | |||||
| namespace rack { | namespace rack { | ||||
| @@ -189,9 +189,9 @@ void RackWidget::fromJson(json_t *rootJ) { | |||||
| // version | // version | ||||
| json_t *versionJ = json_object_get(rootJ, "version"); | json_t *versionJ = json_object_get(rootJ, "version"); | ||||
| if (versionJ) { | if (versionJ) { | ||||
| const char *version = json_string_value(versionJ); | |||||
| if (gApplicationVersion != version) | |||||
| message += stringf("This patch was created with Rack %s. Saving it will convert it to a Rack %s patch.\n\n", version, gApplicationVersion.c_str()); | |||||
| std::string version = json_string_value(versionJ); | |||||
| if (!version.empty() && gApplicationVersion != version) | |||||
| message += stringf("This patch was created with Rack %s. Saving it will convert it to a Rack %s patch.\n\n", version.c_str(), gApplicationVersion.empty() ? "dev" : gApplicationVersion.c_str()); | |||||
| } | } | ||||
| // modules | // modules | ||||
| @@ -404,6 +404,9 @@ struct AddManufacturerMenuItem : MenuItem { | |||||
| for (Model *model : models) { | for (Model *model : models) { | ||||
| AddModuleMenuItem *item = new AddModuleMenuItem(); | AddModuleMenuItem *item = new AddModuleMenuItem(); | ||||
| item->text = model->name; | item->text = model->name; | ||||
| // item->rightText = model->plugin->slug; | |||||
| // if (!model->plugin->version.empty()) | |||||
| // item->rightText += " v" + model->plugin->version; | |||||
| item->model = model; | item->model = model; | ||||
| item->modulePos = modulePos; | item->modulePos = modulePos; | ||||
| menu->pushChild(item); | menu->pushChild(item); | ||||
| @@ -139,13 +139,26 @@ Toolbar::Toolbar() { | |||||
| wireTensionSlider->box.pos = Vec(xPos, margin); | wireTensionSlider->box.pos = Vec(xPos, margin); | ||||
| wireTensionSlider->box.size.x = 150; | wireTensionSlider->box.size.x = 150; | ||||
| wireTensionSlider->label = "Cable tension"; | wireTensionSlider->label = "Cable tension"; | ||||
| // wireTensionSlider->unit = ""; | |||||
| wireTensionSlider->unit = ""; | |||||
| wireTensionSlider->setLimits(0.0, 1.0); | wireTensionSlider->setLimits(0.0, 1.0); | ||||
| wireTensionSlider->setDefaultValue(0.5); | wireTensionSlider->setDefaultValue(0.5); | ||||
| addChild(wireTensionSlider); | addChild(wireTensionSlider); | ||||
| xPos += wireTensionSlider->box.size.x; | xPos += wireTensionSlider->box.size.x; | ||||
| } | } | ||||
| xPos += margin; | |||||
| { | |||||
| zoomSlider = new Slider(); | |||||
| zoomSlider->box.pos = Vec(xPos, margin); | |||||
| zoomSlider->box.size.x = 150; | |||||
| zoomSlider->label = "Zoom"; | |||||
| zoomSlider->unit = "%"; | |||||
| zoomSlider->setLimits(50.0, 200.0); | |||||
| zoomSlider->setDefaultValue(100.0); | |||||
| addChild(zoomSlider); | |||||
| xPos += zoomSlider->box.size.x; | |||||
| } | |||||
| xPos += margin; | xPos += margin; | ||||
| { | { | ||||
| plugLightButton = new RadioButton(); | plugLightButton = new RadioButton(); | ||||
| @@ -1,6 +1,7 @@ | |||||
| #include "app.hpp" | #include "app.hpp" | ||||
| #include "engine.hpp" | #include "engine.hpp" | ||||
| #include "components.hpp" | #include "components.hpp" | ||||
| #include "gui.hpp" | |||||
| namespace rack { | namespace rack { | ||||
| @@ -29,9 +29,12 @@ namespace rack { | |||||
| GLFWwindow *gWindow = NULL; | GLFWwindow *gWindow = NULL; | ||||
| NVGcontext *gVg = NULL; | NVGcontext *gVg = NULL; | ||||
| NVGcontext *gFramebufferVg = NULL; | |||||
| std::shared_ptr<Font> gGuiFont; | std::shared_ptr<Font> gGuiFont; | ||||
| float gPixelRatio = 0.0; | float gPixelRatio = 0.0; | ||||
| bool gAllowCursorLock = true; | bool gAllowCursorLock = true; | ||||
| int gGuiFrame; | |||||
| Vec gMousePos; | |||||
| void windowSizeCallback(GLFWwindow* window, int width, int height) { | void windowSizeCallback(GLFWwindow* window, int width, int height) { | ||||
| @@ -220,15 +223,15 @@ void renderGui() { | |||||
| gPixelRatio = (float)width / windowWidth; | gPixelRatio = (float)width / windowWidth; | ||||
| // Update and render | // Update and render | ||||
| glViewport(0, 0, width, height); | |||||
| glClearColor(0.0, 0.0, 0.0, 1.0); | |||||
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); | |||||
| nvgBeginFrame(gVg, width, height, gPixelRatio); | nvgBeginFrame(gVg, width, height, gPixelRatio); | ||||
| nvgReset(gVg); | nvgReset(gVg); | ||||
| nvgScale(gVg, gPixelRatio, gPixelRatio); | nvgScale(gVg, gPixelRatio, gPixelRatio); | ||||
| gScene->draw(gVg); | gScene->draw(gVg); | ||||
| glViewport(0, 0, width, height); | |||||
| glClearColor(0.0, 0.0, 0.0, 1.0); | |||||
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); | |||||
| nvgEndFrame(gVg); | nvgEndFrame(gVg); | ||||
| glfwSwapBuffers(gWindow); | glfwSwapBuffers(gWindow); | ||||
| } | } | ||||
| @@ -288,6 +291,9 @@ void guiInit() { | |||||
| // gVg = nvgCreateGL3(NVG_ANTIALIAS); | // gVg = nvgCreateGL3(NVG_ANTIALIAS); | ||||
| assert(gVg); | assert(gVg); | ||||
| gFramebufferVg = nvgCreateGL2(NVG_ANTIALIAS); | |||||
| assert(gFramebufferVg); | |||||
| // Set up Blendish | // Set up Blendish | ||||
| gGuiFont = Font::load(assetGlobal("res/DejaVuSans.ttf")); | gGuiFont = Font::load(assetGlobal("res/DejaVuSans.ttf")); | ||||
| bndSetFont(gGuiFont->handle); | bndSetFont(gGuiFont->handle); | ||||
| @@ -298,6 +304,7 @@ void guiDestroy() { | |||||
| gGuiFont.reset(); | gGuiFont.reset(); | ||||
| nvgDeleteGL2(gVg); | nvgDeleteGL2(gVg); | ||||
| // nvgDeleteGL3(gVg); | // nvgDeleteGL3(gVg); | ||||
| nvgDeleteGL2(gFramebufferVg); | |||||
| glfwDestroyWindow(gWindow); | glfwDestroyWindow(gWindow); | ||||
| glfwTerminate(); | glfwTerminate(); | ||||
| } | } | ||||
| @@ -27,6 +27,11 @@ static json_t *settingsToJson() { | |||||
| json_t *tensionJ = json_real(tension); | json_t *tensionJ = json_real(tension); | ||||
| json_object_set_new(rootJ, "wireTension", tensionJ); | json_object_set_new(rootJ, "wireTension", tensionJ); | ||||
| // zoom | |||||
| float zoom = gToolbar->zoomSlider->value; | |||||
| json_t *zoomJ = json_real(zoom); | |||||
| json_object_set_new(rootJ, "zoom", zoomJ); | |||||
| // allowCursorLock | // allowCursorLock | ||||
| json_t *allowCursorLockJ = json_boolean(gAllowCursorLock); | json_t *allowCursorLockJ = json_boolean(gAllowCursorLock); | ||||
| json_object_set_new(rootJ, "allowCursorLock", allowCursorLockJ); | json_object_set_new(rootJ, "allowCursorLock", allowCursorLockJ); | ||||
| @@ -62,6 +67,11 @@ static void settingsFromJson(json_t *rootJ) { | |||||
| if (tensionJ) | if (tensionJ) | ||||
| gToolbar->wireTensionSlider->value = json_number_value(tensionJ); | gToolbar->wireTensionSlider->value = json_number_value(tensionJ); | ||||
| // zoom | |||||
| json_t *zoomJ = json_object_get(rootJ, "zoom"); | |||||
| if (zoomJ) | |||||
| gToolbar->zoomSlider->value = json_number_value(zoomJ); | |||||
| // allowCursorLock | // allowCursorLock | ||||
| json_t *allowCursorLockJ = json_object_get(rootJ, "allowCursorLock"); | json_t *allowCursorLockJ = json_object_get(rootJ, "allowCursorLock"); | ||||
| if (allowCursorLockJ) | if (allowCursorLockJ) | ||||
| @@ -2,12 +2,10 @@ | |||||
| namespace rack { | namespace rack { | ||||
| Vec gMousePos; | |||||
| Widget *gHoveredWidget = NULL; | Widget *gHoveredWidget = NULL; | ||||
| Widget *gDraggedWidget = NULL; | Widget *gDraggedWidget = NULL; | ||||
| Widget *gDragHoveredWidget = NULL; | Widget *gDragHoveredWidget = NULL; | ||||
| Widget *gFocusedWidget = NULL; | Widget *gFocusedWidget = NULL; | ||||
| int gGuiFrame; | |||||
| Scene *gScene = NULL; | Scene *gScene = NULL; | ||||
| @@ -9,9 +9,16 @@ | |||||
| namespace rack { | namespace rack { | ||||
| /** A margin in pixels around the children in the framebuffer | |||||
| This prevents cutting the rendered SVG off on the box edges. | |||||
| */ | |||||
| 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); | ||||
| @@ -32,15 +39,30 @@ FramebufferWidget::~FramebufferWidget() { | |||||
| delete internal; | delete internal; | ||||
| } | } | ||||
| void FramebufferWidget::step() { | |||||
| // Step children before rendering | |||||
| Widget::step(); | |||||
| void FramebufferWidget::draw(NVGcontext *vg) { | |||||
| // Bypass framebuffer rendering entirely | |||||
| // Widget::draw(vg); | |||||
| // return; | |||||
| // Get world transform | |||||
| float xform[6]; | |||||
| nvgCurrentTransform(vg, xform); | |||||
| // Skew is not supported | |||||
| assert(fabsf(xform[1]) < 1e-6); | |||||
| assert(fabsf(xform[2]) < 1e-6); | |||||
| Vec s = Vec(xform[0], xform[3]); | |||||
| 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 the scene to the framebuffer if dirty | |||||
| // Render to framebuffer | |||||
| if (dirty) { | if (dirty) { | ||||
| internal->box.pos = Vec(0, 0); | internal->box.pos = Vec(0, 0); | ||||
| internal->box.size = box.size; | |||||
| internal->box.size = Vec(ceilf(internal->box.size.x), ceilf(internal->box.size.y)); | |||||
| 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); | ||||
| // assert(fbSize.isFinite()); | // assert(fbSize.isFinite()); | ||||
| @@ -50,6 +72,7 @@ void FramebufferWidget::step() { | |||||
| // 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. | |||||
| NVGLUframebuffer *fb = nvgluCreateFramebuffer(gVg, fbSize.x, fbSize.y, NVG_IMAGE_REPEATX | NVG_IMAGE_REPEATY); | NVGLUframebuffer *fb = nvgluCreateFramebuffer(gVg, fbSize.x, fbSize.y, NVG_IMAGE_REPEATX | NVG_IMAGE_REPEATY); | ||||
| if (!fb) | if (!fb) | ||||
| return; | return; | ||||
| @@ -59,49 +82,44 @@ void FramebufferWidget::step() { | |||||
| glViewport(0.0, 0.0, fbSize.x, fbSize.y); | glViewport(0.0, 0.0, fbSize.x, fbSize.y); | ||||
| glClearColor(0.0, 0.0, 0.0, 0.0); | glClearColor(0.0, 0.0, 0.0, 0.0); | ||||
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); | ||||
| nvgBeginFrame(gVg, fbSize.x, fbSize.y, gPixelRatio * oversample); | |||||
| nvgScale(gVg, gPixelRatio * oversample, gPixelRatio * oversample); | |||||
| Widget::draw(gVg); | |||||
| nvgBeginFrame(gFramebufferVg, fbSize.x, fbSize.y, gPixelRatio * oversample); | |||||
| nvgScale(gFramebufferVg, gPixelRatio * oversample, gPixelRatio * oversample); | |||||
| // Use local scaling | |||||
| Vec bFrac = Vec(fmodf(b.x, 1.0), fmodf(b.y, 1.0)); | |||||
| nvgTranslate(gFramebufferVg, bFrac.x, bFrac.y); | |||||
| nvgScale(gFramebufferVg, s.x, s.y); | |||||
| Widget::draw(gFramebufferVg); | |||||
| nvgEndFrame(gVg); | |||||
| nvgEndFrame(gFramebufferVg); | |||||
| nvgluBindFramebuffer(NULL); | nvgluBindFramebuffer(NULL); | ||||
| dirty = false; | dirty = false; | ||||
| } | } | ||||
| } | |||||
| void FramebufferWidget::draw(NVGcontext *vg) { | |||||
| // { | |||||
| // float xform[6]; | |||||
| // nvgCurrentTransform(vg, xform); | |||||
| // printf("%f %f %f %f %f %f\n", xform[0], xform[1], xform[2], xform[3], xform[4], xform[5]); | |||||
| // nvgSave(vg); | |||||
| // nvgResetTransform(vg); | |||||
| // nvgTranslate(vg, xform[5], xform[6]); | |||||
| // nvgBeginPath(vg); | |||||
| // nvgRect(vg, 0, 0, 50, 50); | |||||
| // nvgFillColor(vg, nvgRGBf(1.0, 0.0, 0.0)); | |||||
| // nvgFill(vg); | |||||
| // nvgRestore(vg); | |||||
| // } | |||||
| if (!internal->fb) { | if (!internal->fb) { | ||||
| // Bypass framebuffer cache entirely | |||||
| // Widget::draw(vg); | |||||
| return; | return; | ||||
| } | } | ||||
| // Draw framebuffer image | |||||
| // Draw framebuffer image, using world coordinates | |||||
| b = b.floor(); | |||||
| nvgSave(vg); | |||||
| nvgResetTransform(vg); | |||||
| nvgTranslate(vg, b.x, b.y); | |||||
| nvgBeginPath(vg); | nvgBeginPath(vg); | ||||
| nvgRect(vg, internal->box.pos.x, internal->box.pos.y, internal->box.size.x, internal->box.size.y); | nvgRect(vg, internal->box.pos.x, internal->box.pos.y, internal->box.size.x, internal->box.size.y); | ||||
| NVGpaint paint = nvgImagePattern(vg, internal->box.pos.x, internal->box.pos.y, internal->box.size.x, internal->box.size.y, 0.0, internal->fb->image, 1.0); | NVGpaint paint = nvgImagePattern(vg, internal->box.pos.x, internal->box.pos.y, internal->box.size.x, internal->box.size.y, 0.0, internal->fb->image, 1.0); | ||||
| nvgFillPaint(vg, paint); | nvgFillPaint(vg, paint); | ||||
| nvgFill(vg); | nvgFill(vg); | ||||
| // For debugging bounding box of framebuffer image | |||||
| // nvgFillColor(vg, nvgRGBA(255, 0, 0, 64)); | |||||
| // nvgFill(vg); | |||||
| // For debugging the bounding box of the framebuffer | |||||
| // nvgStrokeWidth(vg, 2.0); | |||||
| // nvgStrokeColor(vg, nvgRGBA(255, 0, 0, 128)); | |||||
| // nvgStroke(vg); | |||||
| nvgRestore(vg); | |||||
| } | } | ||||
| int FramebufferWidget::getImageHandle() { | int FramebufferWidget::getImageHandle() { | ||||
| @@ -1,4 +1,5 @@ | |||||
| #include "widgets.hpp" | #include "widgets.hpp" | ||||
| #include "gui.hpp" | |||||
| namespace rack { | namespace rack { | ||||
| @@ -25,5 +25,11 @@ void Slider::onDragEnd() { | |||||
| guiCursorUnlock(); | guiCursorUnlock(); | ||||
| } | } | ||||
| void Slider::onMouseDownOpaque(int button) { | |||||
| if (button == 1) { | |||||
| setValue(defaultValue); | |||||
| } | |||||
| } | |||||
| } // namespace rack | } // namespace rack | ||||
| @@ -18,6 +18,10 @@ void TextField::draw(NVGcontext *vg) { | |||||
| state = BND_DEFAULT; | state = BND_DEFAULT; | ||||
| bndTextField(vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_NONE, state, -1, text.c_str(), begin, end); | bndTextField(vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_NONE, state, -1, text.c_str(), begin, end); | ||||
| // Draw placeholder text | |||||
| if (text.empty() && state != BND_ACTIVE) { | |||||
| bndIconLabelCaret(vg, 0.0, 0.0, box.size.x, box.size.y, -1, bndGetTheme()->textFieldTheme.itemColor, 13, placeholder.c_str(), bndGetTheme()->textFieldTheme.itemColor, 0, -1); | |||||
| } | |||||
| } | } | ||||
| Widget *TextField::onMouseDown(Vec pos, int button) { | Widget *TextField::onMouseDown(Vec pos, int button) { | ||||
| @@ -1,4 +1,5 @@ | |||||
| #include "widgets.hpp" | #include "widgets.hpp" | ||||
| #include "gui.hpp" | |||||
| namespace rack { | namespace rack { | ||||