way too many people, should not have added in the first place)tags/v0.3.2
| @@ -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); | |||
| @@ -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 | |||
| @@ -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); | |||
| }; | |||
| @@ -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<RackScene*>(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); | |||
| @@ -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<Vec> 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; | |||
| } | |||
| } | |||
| @@ -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; | |||
| { | |||
| @@ -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 | |||
| @@ -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) { | |||
| @@ -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; | |||