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; | bool requested = false; | ||||
Vec requestedPos; | Vec requestedPos; | ||||
Vec dragPos; | Vec dragPos; | ||||
Widget *onMouseMove(Vec pos, Vec mouseRel); | |||||
Widget *onHoverKey(Vec pos, int key); | |||||
void onDragStart(); | void onDragStart(); | ||||
void onDragMove(Vec mouseRel); | void onDragMove(Vec mouseRel); | ||||
void onDragEnd(); | void onDragEnd(); | ||||
@@ -97,7 +99,12 @@ struct RackWidget : OpaqueWidget { | |||||
json_t *toJson(); | json_t *toJson(); | ||||
void fromJson(json_t *root); | 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 step(); | ||||
void draw(NVGcontext *vg); | void draw(NVGcontext *vg); | ||||
@@ -18,13 +18,6 @@ void guiDestroy(); | |||||
void guiRun(); | void guiRun(); | ||||
void guiCursorLock(); | void guiCursorLock(); | ||||
void guiCursorUnlock(); | 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 | } // namespace rack |
@@ -91,15 +91,17 @@ struct Widget { | |||||
*/ | */ | ||||
virtual Widget *onMouseDown(Vec pos, int button); | virtual Widget *onMouseDown(Vec pos, int button); | ||||
virtual Widget *onMouseUp(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 *onMouseMove(Vec pos, Vec mouseRel); | ||||
virtual Widget *onHoverKey(Vec pos, int key); | |||||
/** Called when this widget begins responding to `onMouseMove` events */ | /** Called when this widget begins responding to `onMouseMove` events */ | ||||
virtual void onMouseEnter() {} | virtual void onMouseEnter() {} | ||||
/** Called when another widget begins responding to `onMouseMove` events */ | /** Called when another widget begins responding to `onMouseMove` events */ | ||||
virtual void onMouseLeave() {} | virtual void onMouseLeave() {} | ||||
virtual void onSelect() {} | virtual void onSelect() {} | ||||
virtual void onDeselect() {} | 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); | virtual Widget *onScroll(Vec pos, Vec scrollRel); | ||||
/** Called when a widget responds to `onMouseDown` for a left button press */ | /** Called when a widget responds to `onMouseDown` for a left button press */ | ||||
@@ -364,8 +366,8 @@ struct TextField : OpaqueWidget { | |||||
} | } | ||||
void draw(NVGcontext *vg); | void draw(NVGcontext *vg); | ||||
Widget *onMouseDown(Vec pos, int button); | 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 onSelect(); | ||||
void insertText(std::string newText); | void insertText(std::string newText); | ||||
}; | }; | ||||
@@ -1,6 +1,7 @@ | |||||
#include "app.hpp" | #include "app.hpp" | ||||
#include "engine.hpp" | #include "engine.hpp" | ||||
#include "plugin.hpp" | #include "plugin.hpp" | ||||
#include "gui.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -125,6 +126,7 @@ void ModuleWidget::draw(NVGcontext *vg) { | |||||
Widget::draw(vg); | Widget::draw(vg); | ||||
/* | |||||
// CPU usage text | // CPU usage text | ||||
if (dynamic_cast<RackScene*>(gScene)->toolbar->cpuUsageButton->value > 0.0) { | if (dynamic_cast<RackScene*>(gScene)->toolbar->cpuUsageButton->value > 0.0) { | ||||
float cpuTime = module ? module->cpuTime : 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()); | bndMenuItem(vg, 0.0, 0.0, box.size.x, BND_WIDGET_HEIGHT, BND_DEFAULT, -1, text.c_str()); | ||||
nvgRestore(vg); | nvgRestore(vg); | ||||
} | } | ||||
*/ | |||||
nvgResetScissor(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() { | void ModuleWidget::onDragStart() { | ||||
dragPos = gMousePos.minus(getAbsolutePos()); | dragPos = gMousePos.minus(getAbsolutePos()); | ||||
} | } | ||||
@@ -185,22 +215,14 @@ struct RandomizeMenuItem : MenuItem { | |||||
struct CloneMenuItem : MenuItem { | struct CloneMenuItem : MenuItem { | ||||
ModuleWidget *moduleWidget; | ModuleWidget *moduleWidget; | ||||
void onAction() { | 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 { | struct DeleteMenuItem : MenuItem { | ||||
ModuleWidget *moduleWidget; | ModuleWidget *moduleWidget; | ||||
void onAction() { | void onAction() { | ||||
gRackWidget->moduleContainer->removeChild(moduleWidget); | |||||
gRackWidget->deleteModule(moduleWidget); | |||||
delete moduleWidget; | delete moduleWidget; | ||||
} | } | ||||
}; | }; | ||||
@@ -229,7 +251,7 @@ void ModuleWidget::onMouseDown(int button) { | |||||
menu->pushChild(disconnectItem); | menu->pushChild(disconnectItem); | ||||
CloneMenuItem *cloneItem = new CloneMenuItem(); | CloneMenuItem *cloneItem = new CloneMenuItem(); | ||||
cloneItem->text = "Clone"; | |||||
cloneItem->text = "Duplicate"; | |||||
cloneItem->moduleWidget = this; | cloneItem->moduleWidget = this; | ||||
menu->pushChild(cloneItem); | 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 | // 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; | 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)); | positions.push_back(Vec(x * RACK_GRID_WIDTH, y * RACK_GRID_HEIGHT)); | ||||
} | } | ||||
} | } | ||||
// Sort possible positions by distance to the requested position | // 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) { | std::sort(positions.begin(), positions.end(), [requestedPos](Vec a, Vec b) { | ||||
return a.minus(requestedPos).norm() < b.minus(requestedPos).norm(); | return a.minus(requestedPos).norm() < b.minus(requestedPos).norm(); | ||||
}); | }); | ||||
// Find a position that does not collide | // Find a position that does not collide | ||||
for (Vec pos : positions) { | for (Vec pos : positions) { | ||||
Rect newBox = Rect(pos, module->box.size); | |||||
Rect newBox = Rect(pos, m->box.size); | |||||
bool collides = false; | bool collides = false; | ||||
for (Widget *child2 : moduleContainer->children) { | for (Widget *child2 : moduleContainer->children) { | ||||
if (module == child2) continue; | |||||
if (m == child2) continue; | |||||
if (newBox.intersects(child2->box)) { | if (newBox.intersects(child2->box)) { | ||||
collides = true; | collides = true; | ||||
break; | break; | ||||
@@ -256,7 +276,7 @@ void RackWidget::repositionModule(ModuleWidget *module) { | |||||
} | } | ||||
if (collides) continue; | if (collides) continue; | ||||
module->box.pos = pos; | |||||
m->box.pos = pos; | |||||
break; | break; | ||||
} | } | ||||
} | } | ||||
@@ -139,6 +139,7 @@ Toolbar::Toolbar() { | |||||
xPos += wireTensionSlider->box.size.x; | xPos += wireTensionSlider->box.size.x; | ||||
} | } | ||||
/* | |||||
xPos += margin; | xPos += margin; | ||||
{ | { | ||||
cpuUsageButton = new RadioButton(); | cpuUsageButton = new RadioButton(); | ||||
@@ -148,6 +149,7 @@ Toolbar::Toolbar() { | |||||
addChild(cpuUsageButton); | addChild(cpuUsageButton); | ||||
xPos += cpuUsageButton->box.size.x; | xPos += cpuUsageButton->box.size.x; | ||||
} | } | ||||
*/ | |||||
xPos += margin; | 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) { | void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) { | ||||
if (action == GLFW_PRESS || action == GLFW_REPEAT) { | 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(); | err = glfwInit(); | ||||
assert(err); | 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_MAJOR, 2); | ||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); | glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); | ||||
// glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); | // glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); | ||||
@@ -329,6 +311,14 @@ void guiCursorUnlock() { | |||||
glfwSetInputMode(gWindow, GLFW_CURSOR, GLFW_CURSOR_NORMAL); | 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 | // 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; | char c = codepoint; | ||||
std::string newText(1, c); | std::string newText(1, c); | ||||
insertText(newText); | insertText(newText); | ||||
} | } | ||||
void TextField::onKey(int key) { | |||||
bool TextField::onKey(int key) { | |||||
switch (key) { | switch (key) { | ||||
case GLFW_KEY_BACKSPACE: | case GLFW_KEY_BACKSPACE: | ||||
if (begin < end) { | if (begin < end) { | ||||
@@ -121,6 +121,20 @@ Widget *Widget::onMouseMove(Vec pos, Vec mouseRel) { | |||||
return NULL; | 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) { | Widget *Widget::onScroll(Vec pos, Vec scrollRel) { | ||||
for (auto it = children.rbegin(); it != children.rend(); it++) { | for (auto it = children.rbegin(); it != children.rend(); it++) { | ||||
Widget *child = *it; | Widget *child = *it; | ||||