| @@ -90,10 +90,15 @@ struct RackWidget : OpaqueWidget { | |||
| // Only put WireWidgets in here | |||
| Widget *wireContainer; | |||
| WireWidget *activeWire = NULL; | |||
| std::string lastPath; | |||
| RackWidget(); | |||
| ~RackWidget(); | |||
| void clear(); | |||
| void openDialog(); | |||
| void saveDialog(); | |||
| void saveAsDialog(); | |||
| void savePatch(std::string filename); | |||
| void loadPatch(std::string filename); | |||
| json_t *toJson(); | |||
| @@ -29,5 +29,6 @@ void guiClose(); | |||
| void guiCursorLock(); | |||
| void guiCursorUnlock(); | |||
| bool guiIsModPressed(); | |||
| bool guiIsShiftPressed(); | |||
| } // namespace rack | |||
| @@ -52,6 +52,9 @@ std::string stringf(const char *format, ...); | |||
| /** Truncates and adds "..." to a string, not exceeding `len` characters */ | |||
| std::string ellipsize(std::string s, size_t len); | |||
| std::string extractDirectory(std::string path); | |||
| std::string extractFilename(std::string path); | |||
| /** Opens a URL, also happens to work with PDFs and folders. | |||
| Shell injection is possible, so make sure the URL is trusted or hard coded. | |||
| May block, so open in a new thread. | |||
| @@ -100,10 +100,10 @@ struct Widget { | |||
| virtual void onMouseEnter() {} | |||
| /** Called when another widget begins responding to `onMouseMove` events */ | |||
| virtual void onMouseLeave() {} | |||
| virtual void onSelect() {} | |||
| virtual void onDeselect() {} | |||
| virtual bool onText(int codepoint) {return false;} | |||
| virtual bool onKey(int key) {return false;} | |||
| virtual void onFocus() {} | |||
| virtual void onDefocus() {} | |||
| virtual bool onFocusText(int codepoint) {return false;} | |||
| virtual bool onFocusKey(int key) {return false;} | |||
| virtual Widget *onScroll(Vec pos, Vec scrollRel); | |||
| /** Called when a widget responds to `onMouseDown` for a left button press */ | |||
| @@ -370,9 +370,9 @@ struct TextField : OpaqueWidget { | |||
| } | |||
| void draw(NVGcontext *vg); | |||
| Widget *onMouseDown(Vec pos, int button); | |||
| bool onText(int codepoint); | |||
| bool onKey(int scancode); | |||
| void onSelect(); | |||
| bool onFocusText(int codepoint); | |||
| bool onFocusKey(int scancode); | |||
| void onFocus(); | |||
| void insertText(std::string newText); | |||
| }; | |||
| @@ -408,7 +408,7 @@ extern Vec gMousePos; | |||
| extern Widget *gHoveredWidget; | |||
| extern Widget *gDraggedWidget; | |||
| extern Widget *gDragHoveredWidget; | |||
| extern Widget *gSelectedWidget; | |||
| extern Widget *gFocusedWidget; | |||
| extern int gGuiFrame; | |||
| extern Scene *gScene; | |||
| @@ -155,20 +155,22 @@ Widget *ModuleWidget::onHoverKey(Vec pos, int key) { | |||
| switch (key) { | |||
| case GLFW_KEY_DELETE: | |||
| case GLFW_KEY_BACKSPACE: | |||
| gRackWidget->deleteModule(this); | |||
| this->finalizeEvents(); | |||
| delete this; | |||
| if (!guiIsModPressed() && !guiIsShiftPressed()) { | |||
| gRackWidget->deleteModule(this); | |||
| this->finalizeEvents(); | |||
| delete this; | |||
| } | |||
| break; | |||
| case GLFW_KEY_I: | |||
| if (guiIsModPressed()) | |||
| if (guiIsModPressed() && !guiIsShiftPressed()) | |||
| initialize(); | |||
| break; | |||
| case GLFW_KEY_R: | |||
| if (guiIsModPressed()) | |||
| if (guiIsModPressed() && !guiIsShiftPressed()) | |||
| randomize(); | |||
| break; | |||
| case GLFW_KEY_D: | |||
| if (guiIsModPressed()) | |||
| if (guiIsModPressed() && !guiIsShiftPressed()) | |||
| gRackWidget->cloneModule(this); | |||
| break; | |||
| } | |||
| @@ -70,17 +70,33 @@ void RackScene::draw(NVGcontext *vg) { | |||
| Widget *RackScene::onHoverKey(Vec pos, int key) { | |||
| switch (key) { | |||
| case GLFW_KEY_N: | |||
| if (guiIsModPressed()) { | |||
| if (guiIsModPressed() && !guiIsShiftPressed()) { | |||
| gRackWidget->clear(); | |||
| return this; | |||
| } | |||
| break; | |||
| case GLFW_KEY_Q: | |||
| if (guiIsModPressed()) { | |||
| if (guiIsModPressed() && !guiIsShiftPressed()) { | |||
| guiClose(); | |||
| return this; | |||
| } | |||
| break; | |||
| case GLFW_KEY_O: | |||
| if (guiIsModPressed() && !guiIsShiftPressed()) { | |||
| gRackWidget->openDialog(); | |||
| return this; | |||
| } | |||
| break; | |||
| case GLFW_KEY_S: | |||
| if (guiIsModPressed() && !guiIsShiftPressed()) { | |||
| gRackWidget->saveDialog(); | |||
| return this; | |||
| } | |||
| if (guiIsModPressed() && guiIsShiftPressed()) { | |||
| gRackWidget->saveAsDialog(); | |||
| return this; | |||
| } | |||
| break; | |||
| } | |||
| return Widget::onHoverKey(pos, key); | |||
| @@ -4,6 +4,7 @@ | |||
| #include "engine.hpp" | |||
| #include "plugin.hpp" | |||
| #include "gui.hpp" | |||
| #include "../ext/osdialog/osdialog.h" | |||
| namespace rack { | |||
| @@ -46,6 +47,36 @@ void RackWidget::clear() { | |||
| moduleContainer->clearChildren(); | |||
| } | |||
| void RackWidget::openDialog() { | |||
| std::string dir = lastPath.empty() ? "." : extractDirectory(lastPath); | |||
| char *path = osdialog_file(OSDIALOG_OPEN, dir.c_str(), NULL, NULL); | |||
| if (path) { | |||
| loadPatch(path); | |||
| lastPath = path; | |||
| free(path); | |||
| } | |||
| } | |||
| void RackWidget::saveDialog() { | |||
| if (!lastPath.empty()) { | |||
| savePatch(lastPath); | |||
| } | |||
| else { | |||
| saveAsDialog(); | |||
| } | |||
| } | |||
| void RackWidget::saveAsDialog() { | |||
| std::string dir = lastPath.empty() ? "." : extractDirectory(lastPath); | |||
| char *path = osdialog_file(OSDIALOG_SAVE, dir.c_str(), "Untitled.vcv", NULL); | |||
| if (path) { | |||
| savePatch(path); | |||
| lastPath = path; | |||
| free(path); | |||
| } | |||
| } | |||
| void RackWidget::savePatch(std::string filename) { | |||
| printf("Saving patch %s\n", filename.c_str()); | |||
| FILE *file = fopen(filename.c_str(), "w"); | |||
| @@ -1,7 +1,6 @@ | |||
| #include "app.hpp" | |||
| #include "gui.hpp" | |||
| #include "engine.hpp" | |||
| #include "../ext/osdialog/osdialog.h" | |||
| namespace rack { | |||
| @@ -13,23 +12,21 @@ struct NewItem : MenuItem { | |||
| } | |||
| }; | |||
| struct OpenItem : MenuItem { | |||
| void onAction() { | |||
| gRackWidget->openDialog(); | |||
| } | |||
| }; | |||
| struct SaveItem : MenuItem { | |||
| void onAction() { | |||
| char *path = osdialog_file(OSDIALOG_SAVE, "./patches", "Untitled.vcv", NULL); | |||
| if (path) { | |||
| gRackWidget->savePatch(path); | |||
| free(path); | |||
| } | |||
| gRackWidget->saveDialog(); | |||
| } | |||
| }; | |||
| struct OpenItem : MenuItem { | |||
| struct SaveAsItem : MenuItem { | |||
| void onAction() { | |||
| char *path = osdialog_file(OSDIALOG_OPEN, "./patches", NULL, NULL); | |||
| if (path) { | |||
| gRackWidget->loadPatch(path); | |||
| free(path); | |||
| } | |||
| gRackWidget->saveAsDialog(); | |||
| } | |||
| }; | |||
| @@ -48,11 +45,11 @@ struct FileChoice : ChoiceButton { | |||
| openItem->text = "Open"; | |||
| menu->pushChild(openItem); | |||
| // MenuItem *saveItem = new SaveItem(); | |||
| // saveItem->text = "Save"; | |||
| // menu->pushChild(saveItem); | |||
| MenuItem *saveItem = new SaveItem(); | |||
| saveItem->text = "Save"; | |||
| menu->pushChild(saveItem); | |||
| MenuItem *saveAsItem = new SaveItem(); | |||
| MenuItem *saveAsItem = new SaveAsItem(); | |||
| saveAsItem->text = "Save As"; | |||
| menu->pushChild(saveAsItem); | |||
| } | |||
| @@ -3,6 +3,7 @@ | |||
| #include <algorithm> | |||
| #include <portmidi.h> | |||
| #include "core.hpp" | |||
| #include "gui.hpp" | |||
| using namespace rack; | |||
| @@ -408,3 +409,12 @@ MidiInterfaceWidget::MidiInterfaceWidget() { | |||
| yPos += 37 + margin; | |||
| } | |||
| } | |||
| void MidiInterfaceWidget::step() { | |||
| // Assume QWERTY | |||
| #define MIDI_KEY(key, midi) if (glfwGetKey(gWindow, key)) printf("%d\n", midi); | |||
| // MIDI_KEY(GLFW_KEY_Z, 48); | |||
| ModuleWidget::step(); | |||
| } | |||
| @@ -13,4 +13,5 @@ struct AudioInterfaceWidget : ModuleWidget { | |||
| struct MidiInterfaceWidget : ModuleWidget { | |||
| MidiInterfaceWidget(); | |||
| void step(); | |||
| }; | |||
| @@ -56,14 +56,14 @@ void mouseButtonCallback(GLFWwindow *window, int button, int action, int mods) { | |||
| } | |||
| gDraggedWidget = w; | |||
| if (w != gSelectedWidget) { | |||
| if (gSelectedWidget) { | |||
| w->onDeselect(); | |||
| if (w != gFocusedWidget) { | |||
| if (gFocusedWidget) { | |||
| w->onDefocus(); | |||
| } | |||
| if (w) { | |||
| w->onSelect(); | |||
| w->onFocus(); | |||
| } | |||
| gSelectedWidget = w; | |||
| gFocusedWidget = w; | |||
| } | |||
| } | |||
| } | |||
| @@ -155,15 +155,15 @@ void scrollCallback(GLFWwindow *window, double x, double y) { | |||
| } | |||
| void charCallback(GLFWwindow *window, unsigned int codepoint) { | |||
| if (gSelectedWidget) { | |||
| gSelectedWidget->onText(codepoint); | |||
| if (gFocusedWidget) { | |||
| gFocusedWidget->onFocusText(codepoint); | |||
| } | |||
| } | |||
| void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) { | |||
| if (action == GLFW_PRESS || action == GLFW_REPEAT) { | |||
| // onKey | |||
| if (gSelectedWidget && gSelectedWidget->onKey(key)) | |||
| // onFocusKey | |||
| if (gFocusedWidget && gFocusedWidget->onFocusKey(key)) | |||
| return; | |||
| // onHoverKey | |||
| gScene->onHoverKey(gMousePos, key); | |||
| @@ -218,8 +218,7 @@ void guiInit() { | |||
| // glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); | |||
| glfwWindowHint(GLFW_MAXIMIZED, GLFW_TRUE); | |||
| glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_TRUE); | |||
| std::string title = gApplicationName + " " + gApplicationVersion; | |||
| gWindow = glfwCreateWindow(640, 480, title.c_str(), NULL, NULL); | |||
| gWindow = glfwCreateWindow(640, 480, "", NULL, NULL); | |||
| if (!gWindow) { | |||
| osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, "Cannot open window with OpenGL 2.0 renderer. Does your graphics card support OpenGL 2.0? If so, are the latest drivers installed?"); | |||
| exit(1); | |||
| @@ -280,12 +279,24 @@ void guiRun() { | |||
| double lastTime = 0.0; | |||
| while(!glfwWindowShouldClose(gWindow)) { | |||
| gGuiFrame++; | |||
| // Poll events | |||
| glfwPollEvents(); | |||
| { | |||
| double xpos, ypos; | |||
| glfwGetCursorPos(gWindow, &xpos, &ypos); | |||
| cursorPosCallback(gWindow, xpos, ypos); | |||
| } | |||
| // Set window title | |||
| std::string title = gApplicationName + " " + gApplicationVersion; | |||
| if (!gRackWidget->lastPath.empty()) { | |||
| title += " - "; | |||
| title += extractFilename(gRackWidget->lastPath); | |||
| } | |||
| glfwSetWindowTitle(gWindow, title.c_str()); | |||
| // Step scene | |||
| gScene->step(); | |||
| // Render | |||
| @@ -332,6 +343,10 @@ bool guiIsModPressed() { | |||
| #endif | |||
| } | |||
| bool guiIsShiftPressed() { | |||
| return glfwGetKey(gWindow, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS || glfwGetKey(gWindow, GLFW_KEY_RIGHT_SHIFT) == GLFW_PRESS; | |||
| } | |||
| //////////////////// | |||
| // resources | |||
| @@ -1,7 +1,9 @@ | |||
| #include "util.hpp" | |||
| #include <stdio.h> | |||
| #include <stdarg.h> | |||
| #include <string.h> | |||
| #include <random> | |||
| #include <libgen.h> // for dirname and basename | |||
| #if ARCH_WIN | |||
| #include <windows.h> | |||
| @@ -53,6 +55,20 @@ std::string ellipsize(std::string s, size_t len) { | |||
| return s.substr(0, len - 3) + "..."; | |||
| } | |||
| std::string extractDirectory(std::string path) { | |||
| char *pathDup = strdup(path.c_str()); | |||
| std::string directory = dirname(pathDup); | |||
| free(pathDup); | |||
| return directory; | |||
| } | |||
| std::string extractFilename(std::string path) { | |||
| char *pathDup = strdup(path.c_str()); | |||
| std::string filename = basename(pathDup); | |||
| free(pathDup); | |||
| return filename; | |||
| } | |||
| void openBrowser(std::string url) { | |||
| #if ARCH_LIN | |||
| std::string command = "xdg-open " + url; | |||
| @@ -6,7 +6,7 @@ Vec gMousePos; | |||
| Widget *gHoveredWidget = NULL; | |||
| Widget *gDraggedWidget = NULL; | |||
| Widget *gDragHoveredWidget = NULL; | |||
| Widget *gSelectedWidget = NULL; | |||
| Widget *gFocusedWidget = NULL; | |||
| int gGuiFrame; | |||
| Scene *gScene = NULL; | |||
| @@ -10,7 +10,7 @@ namespace rack { | |||
| void TextField::draw(NVGcontext *vg) { | |||
| BNDwidgetState state; | |||
| if (this == gSelectedWidget) | |||
| if (this == gFocusedWidget) | |||
| state = BND_ACTIVE; | |||
| else if (this == gHoveredWidget) | |||
| state = BND_HOVER; | |||
| @@ -29,14 +29,14 @@ Widget *TextField::onMouseDown(Vec pos, int button) { | |||
| } | |||
| bool TextField::onText(int codepoint) { | |||
| bool TextField::onFocusText(int codepoint) { | |||
| char c = codepoint; | |||
| std::string newText(1, c); | |||
| insertText(newText); | |||
| return true; | |||
| } | |||
| bool TextField::onKey(int key) { | |||
| bool TextField::onFocusKey(int key) { | |||
| switch (key) { | |||
| case GLFW_KEY_BACKSPACE: | |||
| if (begin < end) { | |||
| @@ -103,7 +103,7 @@ bool TextField::onKey(int key) { | |||
| return true; | |||
| } | |||
| void TextField::onSelect() { | |||
| void TextField::onFocus() { | |||
| begin = 0; | |||
| end = text.size(); | |||
| } | |||
| @@ -12,7 +12,7 @@ Widget::~Widget() { | |||
| if (gHoveredWidget == this) gHoveredWidget = NULL; | |||
| if (gDraggedWidget == this) gDraggedWidget = NULL; | |||
| if (gDragHoveredWidget == this) gDragHoveredWidget = NULL; | |||
| if (gSelectedWidget == this) gSelectedWidget = NULL; | |||
| if (gFocusedWidget == this) gFocusedWidget = NULL; | |||
| clearChildren(); | |||
| } | |||
| @@ -76,9 +76,9 @@ void Widget::finalizeEvents() { | |||
| if (gDragHoveredWidget == this) { | |||
| gDragHoveredWidget = NULL; | |||
| } | |||
| if (gSelectedWidget == this) { | |||
| gSelectedWidget->onDeselect(); | |||
| gSelectedWidget = NULL; | |||
| if (gFocusedWidget == this) { | |||
| gFocusedWidget->onDefocus(); | |||
| gFocusedWidget = NULL; | |||
| } | |||
| for (Widget *child : children) { | |||
| child->finalizeEvents(); | |||