diff --git a/include/app.hpp b/include/app.hpp index a13e453f..c879e8fa 100644 --- a/include/app.hpp +++ b/include/app.hpp @@ -57,6 +57,8 @@ struct ModuleWidget : OpaqueWidget { bool requested = false; Vec requestedPos; Vec dragPos; + Widget *onMouseMove(Vec pos, Vec mouseRel); + Widget *onHoverKey(Vec pos, int key); void onDragStart(); void onDragMove(Vec mouseRel); void onDragEnd(); @@ -97,7 +99,12 @@ struct RackWidget : OpaqueWidget { json_t *toJson(); void fromJson(json_t *root); - void repositionModule(ModuleWidget *module); + void addModule(ModuleWidget *m); + /** Transfers ownership to the caller so they must `delete` it if that is the intension */ + void deleteModule(ModuleWidget *m); + void cloneModule(ModuleWidget *m); + /** Moves a module to the closest non-colliding position */ + void repositionModule(ModuleWidget *m); void step(); void draw(NVGcontext *vg); diff --git a/include/gui.hpp b/include/gui.hpp index 596c8b68..e0b50808 100644 --- a/include/gui.hpp +++ b/include/gui.hpp @@ -18,13 +18,6 @@ void guiDestroy(); void guiRun(); void guiCursorLock(); void guiCursorUnlock(); - -inline bool guiIsModPressed() { -#ifdef ARCH_MAC - return glfwGetKey(gWindow, GLFW_KEY_LEFT_SUPER) == GLFW_PRESS || glfwGetKey(gWindow, GLFW_KEY_RIGHT_SUPER) == GLFW_PRESS; -#else - return glfwGetKey(gWindow, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS || glfwGetKey(gWindow, GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS; -#endif -} +bool guiIsModPressed(); } // namespace rack diff --git a/include/widgets.hpp b/include/widgets.hpp index 7f0e7bff..2f509606 100644 --- a/include/widgets.hpp +++ b/include/widgets.hpp @@ -91,15 +91,17 @@ struct Widget { */ virtual Widget *onMouseDown(Vec pos, int button); virtual Widget *onMouseUp(Vec pos, int button); + /** Called on every frame, even if mouseRel = Vec(0, 0) */ virtual Widget *onMouseMove(Vec pos, Vec mouseRel); + virtual Widget *onHoverKey(Vec pos, int key); /** Called when this widget begins responding to `onMouseMove` events */ virtual void onMouseEnter() {} /** Called when another widget begins responding to `onMouseMove` events */ virtual void onMouseLeave() {} virtual void onSelect() {} virtual void onDeselect() {} - virtual void onText(int codepoint) {} - virtual void onKey(int key) {} + virtual bool onText(int codepoint) {return false;} + virtual bool onKey(int key) {return false;} virtual Widget *onScroll(Vec pos, Vec scrollRel); /** Called when a widget responds to `onMouseDown` for a left button press */ @@ -364,8 +366,8 @@ struct TextField : OpaqueWidget { } void draw(NVGcontext *vg); Widget *onMouseDown(Vec pos, int button); - void onText(int codepoint); - void onKey(int scancode); + bool onText(int codepoint); + bool onKey(int scancode); void onSelect(); void insertText(std::string newText); }; diff --git a/src/app/ModuleWidget.cpp b/src/app/ModuleWidget.cpp index dfd64bd9..ba48f049 100644 --- a/src/app/ModuleWidget.cpp +++ b/src/app/ModuleWidget.cpp @@ -1,6 +1,7 @@ #include "app.hpp" #include "engine.hpp" #include "plugin.hpp" +#include "gui.hpp" namespace rack { @@ -125,6 +126,7 @@ void ModuleWidget::draw(NVGcontext *vg) { Widget::draw(vg); +/* // CPU usage text if (dynamic_cast(gScene)->toolbar->cpuUsageButton->value > 0.0) { float cpuTime = module ? module->cpuTime : 0.0; @@ -145,10 +147,38 @@ void ModuleWidget::draw(NVGcontext *vg) { bndMenuItem(vg, 0.0, 0.0, box.size.x, BND_WIDGET_HEIGHT, BND_DEFAULT, -1, text.c_str()); nvgRestore(vg); } +*/ nvgResetScissor(vg); } +Widget *ModuleWidget::onMouseMove(Vec pos, Vec mouseRel) { + return OpaqueWidget::onMouseMove(pos, mouseRel); +} + +Widget *ModuleWidget::onHoverKey(Vec pos, int key) { + switch (key) { + case GLFW_KEY_DELETE: + case GLFW_KEY_BACKSPACE: + gRackWidget->deleteModule(this); + delete this; + break; + case GLFW_KEY_I: + if (guiIsModPressed()) + initialize(); + break; + case GLFW_KEY_R: + if (guiIsModPressed()) + randomize(); + break; + case GLFW_KEY_D: + if (guiIsModPressed()) + gRackWidget->cloneModule(this); + break; + } + return NULL; +} + void ModuleWidget::onDragStart() { dragPos = gMousePos.minus(getAbsolutePos()); } @@ -185,22 +215,14 @@ struct RandomizeMenuItem : MenuItem { struct CloneMenuItem : MenuItem { ModuleWidget *moduleWidget; void onAction() { - // Create new module from model - ModuleWidget *clonedModuleWidget = moduleWidget->model->createModuleWidget(); - // JSON serialization is the most straightforward way to do this - json_t *moduleJ = moduleWidget->toJson(); - clonedModuleWidget->fromJson(moduleJ); - json_decref(moduleJ); - clonedModuleWidget->requestedPos = moduleWidget->box.pos; - clonedModuleWidget->requested = true; - gRackWidget->moduleContainer->addChild(clonedModuleWidget); + gRackWidget->cloneModule(moduleWidget); } }; struct DeleteMenuItem : MenuItem { ModuleWidget *moduleWidget; void onAction() { - gRackWidget->moduleContainer->removeChild(moduleWidget); + gRackWidget->deleteModule(moduleWidget); delete moduleWidget; } }; @@ -229,7 +251,7 @@ void ModuleWidget::onMouseDown(int button) { menu->pushChild(disconnectItem); CloneMenuItem *cloneItem = new CloneMenuItem(); - cloneItem->text = "Clone"; + cloneItem->text = "Duplicate"; cloneItem->moduleWidget = this; menu->pushChild(cloneItem); diff --git a/src/app/RackWidget.cpp b/src/app/RackWidget.cpp index 8b2aadae..1ab354bf 100644 --- a/src/app/RackWidget.cpp +++ b/src/app/RackWidget.cpp @@ -226,29 +226,49 @@ void RackWidget::fromJson(json_t *rootJ) { } } -void RackWidget::repositionModule(ModuleWidget *module) { +void RackWidget::addModule(ModuleWidget *m) { + moduleContainer->addChild(m); +} + +void RackWidget::deleteModule(ModuleWidget *m) { + moduleContainer->removeChild(m); +} + +void RackWidget::cloneModule(ModuleWidget *m) { + // Create new module from model + ModuleWidget *clonedModuleWidget = m->model->createModuleWidget(); + // JSON serialization is the most straightforward way to do this + json_t *moduleJ = m->toJson(); + clonedModuleWidget->fromJson(moduleJ); + json_decref(moduleJ); + clonedModuleWidget->requestedPos = m->box.pos; + clonedModuleWidget->requested = true; + addModule(clonedModuleWidget); +} + +void RackWidget::repositionModule(ModuleWidget *m) { // Create possible positions - int x0 = roundf(module->requestedPos.x / RACK_GRID_WIDTH); - int y0 = roundf(module->requestedPos.y / RACK_GRID_HEIGHT); + int x0 = roundf(m->requestedPos.x / RACK_GRID_WIDTH); + int y0 = roundf(m->requestedPos.y / RACK_GRID_HEIGHT); std::vector positions; - for (int y = maxi(0, y0 - 2); y < y0 + 2; y++) { - for (int x = maxi(0, x0 - 40); x < x0 + 40; x++) { + for (int y = maxi(0, y0 - 4); y < y0 + 4; y++) { + for (int x = maxi(0, x0 - 200); x < x0 + 200; x++) { positions.push_back(Vec(x * RACK_GRID_WIDTH, y * RACK_GRID_HEIGHT)); } } // Sort possible positions by distance to the requested position - Vec requestedPos = module->requestedPos; + Vec requestedPos = m->requestedPos; std::sort(positions.begin(), positions.end(), [requestedPos](Vec a, Vec b) { return a.minus(requestedPos).norm() < b.minus(requestedPos).norm(); }); // Find a position that does not collide for (Vec pos : positions) { - Rect newBox = Rect(pos, module->box.size); + Rect newBox = Rect(pos, m->box.size); bool collides = false; for (Widget *child2 : moduleContainer->children) { - if (module == child2) continue; + if (m == child2) continue; if (newBox.intersects(child2->box)) { collides = true; break; @@ -256,7 +276,7 @@ void RackWidget::repositionModule(ModuleWidget *module) { } if (collides) continue; - module->box.pos = pos; + m->box.pos = pos; break; } } diff --git a/src/app/Toolbar.cpp b/src/app/Toolbar.cpp index 738efeb9..c86e2007 100644 --- a/src/app/Toolbar.cpp +++ b/src/app/Toolbar.cpp @@ -139,6 +139,7 @@ Toolbar::Toolbar() { xPos += wireTensionSlider->box.size.x; } + /* xPos += margin; { cpuUsageButton = new RadioButton(); @@ -148,6 +149,7 @@ Toolbar::Toolbar() { addChild(cpuUsageButton); xPos += cpuUsageButton->box.size.x; } + */ xPos += margin; { diff --git a/src/gui.cpp b/src/gui.cpp index c1fa556a..7fcb1994 100644 --- a/src/gui.cpp +++ b/src/gui.cpp @@ -157,34 +157,13 @@ void charCallback(GLFWwindow *window, unsigned int codepoint) { } } -// static int lastWindowX, lastWindowY, lastWindowWidth, lastWindowHeight; - void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) { if (action == GLFW_PRESS || action == GLFW_REPEAT) { - if (key == GLFW_KEY_F11 || key == GLFW_KEY_ESCAPE) { - /* - // Toggle fullscreen - GLFWmonitor *monitor = glfwGetWindowMonitor(gWindow); - if (monitor) { - // Window mode - glfwSetWindowMonitor(gWindow, NULL, lastWindowX, lastWindowY, lastWindowWidth, lastWindowHeight, 0); - } - else { - // Fullscreen - glfwGetWindowPos(gWindow, &lastWindowX, &lastWindowY); - glfwGetWindowSize(gWindow, &lastWindowWidth, &lastWindowHeight); - monitor = glfwGetPrimaryMonitor(); - assert(monitor); - const GLFWvidmode *mode = glfwGetVideoMode(monitor); - glfwSetWindowMonitor(gWindow, monitor, 0, 0, mode->width, mode->height, mode->refreshRate); - } - */ - } - else { - if (gSelectedWidget) { - gSelectedWidget->onKey(key); - } - } + // onKey + if (gSelectedWidget && gSelectedWidget->onKey(key)) + return; + // onHoverKey + gScene->onHoverKey(gMousePos, key); } } @@ -228,6 +207,9 @@ void guiInit() { err = glfwInit(); assert(err); + const char *glVersion = (const char *)glGetString(GL_VERSION); + printf("OpenGL version %s\n", glVersion); + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); // glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); @@ -329,6 +311,14 @@ void guiCursorUnlock() { glfwSetInputMode(gWindow, GLFW_CURSOR, GLFW_CURSOR_NORMAL); } +bool guiIsModPressed() { +#ifdef ARCH_MAC + return glfwGetKey(gWindow, GLFW_KEY_LEFT_SUPER) == GLFW_PRESS || glfwGetKey(gWindow, GLFW_KEY_RIGHT_SUPER) == GLFW_PRESS; +#else + return glfwGetKey(gWindow, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS || glfwGetKey(gWindow, GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS; +#endif +} + //////////////////// // resources diff --git a/src/widgets/TextField.cpp b/src/widgets/TextField.cpp index 25415c27..afc7d224 100644 --- a/src/widgets/TextField.cpp +++ b/src/widgets/TextField.cpp @@ -29,13 +29,13 @@ Widget *TextField::onMouseDown(Vec pos, int button) { } -void TextField::onText(int codepoint) { +bool TextField::onText(int codepoint) { char c = codepoint; std::string newText(1, c); insertText(newText); } -void TextField::onKey(int key) { +bool TextField::onKey(int key) { switch (key) { case GLFW_KEY_BACKSPACE: if (begin < end) { diff --git a/src/widgets/Widget.cpp b/src/widgets/Widget.cpp index 126d53a2..6c757a52 100644 --- a/src/widgets/Widget.cpp +++ b/src/widgets/Widget.cpp @@ -121,6 +121,20 @@ Widget *Widget::onMouseMove(Vec pos, Vec mouseRel) { return NULL; } +Widget *Widget::onHoverKey(Vec pos, int key) { + for (auto it = children.rbegin(); it != children.rend(); it++) { + Widget *child = *it; + if (!child->visible) + continue; + if (child->box.contains(pos)) { + Widget *w = child->onHoverKey(pos.minus(child->box.pos), key); + if (w) + return w; + } + } + return NULL; +} + Widget *Widget::onScroll(Vec pos, Vec scrollRel) { for (auto it = children.rbegin(); it != children.rend(); it++) { Widget *child = *it;