@@ -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(); | |||