Browse Source

Work on toolbar, event context

tags/v1.0.0
Andrew Belt 5 years ago
parent
commit
f5601d2042
38 changed files with 932 additions and 835 deletions
  1. +1
    -1
      include/app/ParamQuantity.hpp
  2. +0
    -18
      include/app/PluginManagerWidget.hpp
  3. +0
    -1
      include/app/RackWidget.hpp
  4. +1
    -0
      include/common.hpp
  5. +0
    -1
      include/engine/Engine.hpp
  6. +7
    -3
      include/event.hpp
  7. +2
    -1
      include/helpers.hpp
  8. +0
    -1
      include/logger.hpp
  9. +0
    -2
      include/rack.hpp
  10. +8
    -0
      include/settings.hpp
  11. +0
    -1
      include/ui/Button.hpp
  12. +6
    -62
      include/ui/Menu.hpp
  13. +6
    -55
      include/ui/MenuItem.hpp
  14. +1
    -0
      include/ui/SequentialLayout.hpp
  15. +10
    -192
      include/ui/TextField.hpp
  16. +0
    -1
      src/app/Knob.cpp
  17. +0
    -1
      src/app/ModuleBrowser.cpp
  18. +3
    -3
      src/app/ModuleWidget.cpp
  19. +8
    -0
      src/app/ParamQuantity.cpp
  20. +0
    -241
      src/app/PluginManagerWidget.cpp
  21. +0
    -2
      src/app/RackWidget.cpp
  22. +0
    -1
      src/app/Scene.cpp
  23. +415
    -134
      src/app/Toolbar.cpp
  24. +3
    -4
      src/app/WireWidget.cpp
  25. +0
    -1
      src/audio.cpp
  26. +0
    -1
      src/bridge.cpp
  27. +2
    -1
      src/engine/Engine.cpp
  28. +96
    -79
      src/event.cpp
  29. +1
    -1
      src/logger.cpp
  30. +0
    -1
      src/main.cpp
  31. +0
    -1
      src/plugin.cpp
  32. +0
    -1
      src/plugin/Model.cpp
  33. +0
    -1
      src/plugin/Plugin.cpp
  34. +25
    -22
      src/settings.cpp
  35. +62
    -0
      src/ui/Menu.cpp
  36. +69
    -0
      src/ui/MenuItem.cpp
  37. +206
    -0
      src/ui/TextField.cpp
  38. +0
    -1
      src/window.cpp

+ 1
- 1
include/app/ParamQuantity.hpp View File

@@ -26,7 +26,7 @@ struct ParamQuantity : Quantity {
float getMaxValue() override;
float getDefaultValue() override;
float getDisplayValue() override;
void setDisplayValue(float displayValue) override ;
void setDisplayValue(float displayValue) override;
int getDisplayPrecision() override;
std::string getLabel() override;
std::string getUnit() override;


+ 0
- 18
include/app/PluginManagerWidget.hpp View File

@@ -1,18 +0,0 @@
#pragma once
#include "app/common.hpp"
#include "widgets/Widget.hpp"


namespace rack {


struct PluginManagerWidget : virtual Widget {
Widget *loginWidget;
Widget *manageWidget;
Widget *downloadWidget;
PluginManagerWidget();
void step() override;
};


} // namespace rack

+ 0
- 1
include/app/RackWidget.hpp View File

@@ -17,7 +17,6 @@ struct RackWidget : OpaqueWidget {
WireContainer *wireContainer;
std::string lastPath;
math::Vec lastMousePos;
bool lockModules = false;

RackWidget();
~RackWidget();


+ 1
- 0
include/common.hpp View File

@@ -1,4 +1,5 @@
#pragma once
#include "logger.hpp"

// Include most of the C stdlib for convenience
#include <cstdlib>


+ 0
- 1
include/engine/Engine.hpp View File

@@ -15,7 +15,6 @@ struct Engine {
std::vector<Module*> modules;
std::vector<Wire*> wires;
bool paused = false;
bool powerMeter = false;

struct Internal;
Internal *internal;


+ 7
- 3
include/event.hpp View File

@@ -153,7 +153,6 @@ struct DragMove : Event {


/** Occurs every frame when the mouse is hovering over a Widget while dragging.
Must consume to allow DragEnter, DragLeave, and DragDrop to occur.
*/
struct DragHover : Event, Position {
/** Change in mouse position since the last frame. Can be zero. */
@@ -217,6 +216,13 @@ struct Context {
/** For middle-click dragging */
Widget *scrollWidget = NULL;

void setHovered(Widget *w);
void setDragged(Widget *w);
void setDragHovered(Widget *w);
void setSelected(Widget *w);
/** Prepares a widget for deletion */
void finalizeWidget(Widget *w);

void handleButton(math::Vec pos, int button, int action, int mods);
void handleHover(math::Vec pos, math::Vec mouseDelta);
void handleLeave();
@@ -225,8 +231,6 @@ struct Context {
void handleKey(math::Vec pos, int key, int scancode, int action, int mods);
void handleDrop(math::Vec pos, std::vector<std::string> paths);
void handleZoom();
/** Prepares a widget for deletion */
void finalizeWidget(Widget *w);
};




+ 2
- 1
include/helpers.hpp View File

@@ -5,6 +5,7 @@
#include "app/Port.hpp"
#include "app/ParamQuantity.hpp"
#include "app/ParamWidget.hpp"
#include "app/Scene.hpp"
#include "engine/Module.hpp"
#include "context.hpp"
#include "window.hpp"
@@ -154,7 +155,7 @@ inline Menu *createMenu() {
MenuOverlay *menuOverlay = new MenuOverlay;
menuOverlay->addChild(o);

context()->event->rootWidget->addChild(menuOverlay);
context()->scene->addChild(menuOverlay);
return o;
}



+ 0
- 1
include/logger.hpp View File

@@ -1,5 +1,4 @@
#pragma once
#include "common.hpp"


/** Example usage:


+ 0
- 2
include/rack.hpp View File

@@ -3,7 +3,6 @@
#include "common.hpp"
#include "math.hpp"
#include "string.hpp"
#include "logger.hpp"
#include "system.hpp"
#include "random.hpp"
#include "network.hpp"
@@ -52,7 +51,6 @@
#include "app/MomentarySwitch.hpp"
#include "app/MultiLightWidget.hpp"
#include "app/ParamWidget.hpp"
#include "app/PluginManagerWidget.hpp"
#include "app/Port.hpp"
#include "app/RackRail.hpp"
#include "app/Scene.hpp"


+ 8
- 0
include/settings.hpp View File

@@ -10,5 +10,13 @@ void save(std::string filename);
void load(std::string filename);


extern float zoom;
extern float wireOpacity;
extern float wireTension;
extern bool powerMeter;
extern bool lockModules;
extern bool checkVersion;


} // namespace settings
} // namespace rack

+ 0
- 1
include/ui/Button.hpp View File

@@ -24,7 +24,6 @@ struct Button : OpaqueWidget {

void draw(NVGcontext *vg) override {
bndToolButton(vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_NONE, state, -1, text.c_str());
Widget::draw(vg);
}

void onEnter(event::Enter &e) override {


+ 6
- 62
include/ui/Menu.hpp View File

@@ -13,68 +13,12 @@ struct Menu : OpaqueWidget {
/** The entry which created the child menu */
MenuEntry *activeEntry = NULL;

Menu() {
box.size = math::Vec(0, 0);
}

~Menu() {
setChildMenu(NULL);
}

/** Deprecated. Just use addChild(child) instead */
DEPRECATED void pushChild(Widget *child) {
addChild(child);
}

void setChildMenu(Menu *menu) {
if (childMenu) {
if (childMenu->parent)
childMenu->parent->removeChild(childMenu);
delete childMenu;
childMenu = NULL;
}
if (menu) {
childMenu = menu;
assert(parent);
parent->addChild(childMenu);
}
}

void step() override {
Widget::step();

// Set positions of children
box.size = math::Vec(0, 0);
for (Widget *child : children) {
if (!child->visible)
continue;
// Increment height, set position of child
child->box.pos = math::Vec(0, box.size.y);
box.size.y += child->box.size.y;
// Increase width based on maximum width of child
if (child->box.size.x > box.size.x) {
box.size.x = child->box.size.x;
}
}

// Resize widths of children
for (Widget *child : children) {
child->box.size.x = box.size.x;
}
}

void draw(NVGcontext *vg) override {
bndMenuBackground(vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_NONE);
Widget::draw(vg);
}

void onHoverScroll(event::HoverScroll &e) override {
if (!parent)
return;
if (!parent->box.contains(box))
box.pos.y += e.scrollDelta.y;
// e.consumed = true;
}
Menu();
~Menu();
void setChildMenu(Menu *menu);
void step() override;
void draw(NVGcontext *vg) override;
void onHoverScroll(event::HoverScroll &e) override;
};




+ 6
- 55
include/ui/MenuItem.hpp View File

@@ -15,63 +15,14 @@ namespace rack {
struct MenuItem : MenuEntry {
std::string text;
std::string rightText;
bool disabled = false;

void draw(NVGcontext *vg) override {
// Get state
BNDwidgetState state = (context()->event->hoveredWidget == this) ? BND_HOVER : BND_DEFAULT;
Menu *parentMenu = dynamic_cast<Menu*>(parent);
if (parentMenu && parentMenu->activeEntry == this) {
state = BND_ACTIVE;
}

bndMenuItem(vg, 0.0, 0.0, box.size.x, box.size.y, state, -1, text.c_str());

float x = box.size.x - bndLabelWidth(vg, -1, rightText.c_str());
NVGcolor rightColor = (state == BND_DEFAULT) ? bndGetTheme()->menuTheme.textColor : bndGetTheme()->menuTheme.textSelectedColor;
bndIconLabelValue(vg, x, 0.0, box.size.x, box.size.y, -1, rightColor, BND_LEFT, BND_LABEL_FONT_SIZE, rightText.c_str(), NULL);
}

void step() override {
// Add 10 more pixels because measurements on high-DPI screens are sometimes too small for some reason
const float rightPadding = 10.0;
// HACK use context()->window->vg from the window.
// All this does is inspect the font, so it shouldn't modify context()->window->vg and should work when called from a FramebufferWidget for example.
box.size.x = bndLabelWidth(context()->window->vg, -1, text.c_str()) + bndLabelWidth(context()->window->vg, -1, rightText.c_str()) + rightPadding;
Widget::step();
}

void draw(NVGcontext *vg) override;
void step() override;
void onEnter(event::Enter &e) override;
void onDragDrop(event::DragDrop &e) override;
void doAction();
virtual Menu *createChildMenu() {return NULL;}

void onEnter(event::Enter &e) override {
Menu *parentMenu = dynamic_cast<Menu*>(parent);
if (!parentMenu)
return;

parentMenu->activeEntry = NULL;

// Try to create child menu
Menu *childMenu = createChildMenu();
if (childMenu) {
parentMenu->activeEntry = this;
childMenu->box.pos = parent->box.pos.plus(box.getTopRight());
}
parentMenu->setChildMenu(childMenu);
}

void onDragDrop(event::DragDrop &e) override {
if (e.origin != this)
return;

event::Action eAction;
// Consume event by default, but allow action to un-consume it to prevent the menu from being removed.
eAction.target = this;
onAction(eAction);
if (!eAction.target)
return;

Widget *overlay = getAncestorOfType<MenuOverlay>();
overlay->requestedDelete = true;
}
};




+ 1
- 0
include/ui/SequentialLayout.hpp View File

@@ -13,6 +13,7 @@ struct SequentialLayout : virtual Widget {
VERTICAL_ORIENTATION,
};
Orientation orientation = HORIZONTAL_ORIENTATION;

enum Alignment {
LEFT_ALIGNMENT,
CENTER_ALIGNMENT,


+ 10
- 192
include/ui/TextField.hpp View File

@@ -19,202 +19,20 @@ struct TextField : OpaqueWidget {
*/
int selection = 0;

TextField() {
box.size.y = BND_WIDGET_HEIGHT;
}

void draw(NVGcontext *vg) override {
nvgScissor(vg, 0, 0, box.size.x, box.size.y);

BNDwidgetState state;
if (this == context()->event->selectedWidget)
state = BND_ACTIVE;
else if (this == context()->event->hoveredWidget)
state = BND_HOVER;
else
state = BND_DEFAULT;

int begin = std::min(cursor, selection);
int end = std::max(cursor, selection);
bndTextField(vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_NONE, state, -1, text.c_str(), begin, end);
// Draw placeholder text
if (text.empty() && state != BND_ACTIVE) {
bndIconLabelCaret(vg, 0.0, 0.0, box.size.x, box.size.y, -1, bndGetTheme()->textFieldTheme.itemColor, 13, placeholder.c_str(), bndGetTheme()->textFieldTheme.itemColor, 0, -1);
}

nvgResetScissor(vg);
}

void onButton(event::Button &e) override {
if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT) {
cursor = selection = getTextPosition(e.pos);
}
OpaqueWidget::onButton(e);
}

void onHover(event::Hover &e) override {
if (this == context()->event->draggedWidget) {
int pos = getTextPosition(e.pos);
if (pos != selection) {
cursor = pos;
}
}
OpaqueWidget::onHover(e);
}

void onEnter(event::Enter &e) override {
e.target = this;
}

void onSelectText(event::SelectText &e) override {
if (e.codepoint < 128) {
std::string newText(1, (char) e.codepoint);
insertText(newText);
}
e.target = this;
}

void onSelectKey(event::SelectKey &e) override {
switch (e.key) {
case GLFW_KEY_BACKSPACE: {
if (cursor == selection) {
cursor--;
if (cursor >= 0) {
text.erase(cursor, 1);
event::Change eChange;
onChange(eChange);
}
selection = cursor;
}
else {
int begin = std::min(cursor, selection);
text.erase(begin, std::abs(selection - cursor));
event::Change eChange;
onChange(eChange);
cursor = selection = begin;
}
} break;
case GLFW_KEY_DELETE: {
if (cursor == selection) {
text.erase(cursor, 1);
event::Change eChange;
onChange(eChange);
}
else {
int begin = std::min(cursor, selection);
text.erase(begin, std::abs(selection - cursor));
event::Change eChange;
onChange(eChange);
cursor = selection = begin;
}
} break;
case GLFW_KEY_LEFT: {
if (context()->window->isModPressed()) {
while (--cursor > 0) {
if (text[cursor] == ' ')
break;
}
}
else {
cursor--;
}
if (!context()->window->isShiftPressed()) {
selection = cursor;
}
} break;
case GLFW_KEY_RIGHT: {
if (context()->window->isModPressed()) {
while (++cursor < (int) text.size()) {
if (text[cursor] == ' ')
break;
}
}
else {
cursor++;
}
if (!context()->window->isShiftPressed()) {
selection = cursor;
}
} break;
case GLFW_KEY_HOME: {
selection = cursor = 0;
} break;
case GLFW_KEY_END: {
selection = cursor = text.size();
} break;
case GLFW_KEY_V: {
if (context()->window->isModPressed()) {
const char *newText = glfwGetClipboardString(context()->window->win);
if (newText)
insertText(newText);
}
} break;
case GLFW_KEY_X: {
if (context()->window->isModPressed()) {
if (cursor != selection) {
int begin = std::min(cursor, selection);
std::string selectedText = text.substr(begin, std::abs(selection - cursor));
glfwSetClipboardString(context()->window->win, selectedText.c_str());
insertText("");
}
}
} break;
case GLFW_KEY_C: {
if (context()->window->isModPressed()) {
if (cursor != selection) {
int begin = std::min(cursor, selection);
std::string selectedText = text.substr(begin, std::abs(selection - cursor));
glfwSetClipboardString(context()->window->win, selectedText.c_str());
}
}
} break;
case GLFW_KEY_A: {
if (context()->window->isModPressed()) {
selection = 0;
cursor = text.size();
}
} break;
case GLFW_KEY_ENTER: {
if (multiline) {
insertText("\n");
}
else {
event::Action eAction;
onAction(eAction);
}
} break;
}

cursor = math::clamp(cursor, 0, (int) text.size());
selection = math::clamp(selection, 0, (int) text.size());
e.target = this;
}
TextField();
void draw(NVGcontext *vg) override;
void onButton(event::Button &e) override;
void onHover(event::Hover &e) override;
void onEnter(event::Enter &e) override;
void onSelectText(event::SelectText &e) override;
void onSelectKey(event::SelectKey &e) override;

/** Inserts text at the cursor, replacing the selection if necessary */
void insertText(std::string text) {
if (cursor != selection) {
int begin = std::min(cursor, selection);
this->text.erase(begin, std::abs(selection - cursor));
cursor = selection = begin;
}
this->text.insert(cursor, text);
cursor += text.size();
selection = cursor;
event::Change eChange;
onChange(eChange);
}
void insertText(std::string text);

/** Replaces the entire text */
void setText(std::string text) {
this->text = text;
selection = cursor = text.size();
event::Change eChange;
onChange(eChange);
}

virtual int getTextPosition(math::Vec mousePos) {
return bndTextFieldTextPosition(context()->window->vg, 0.0, 0.0, box.size.x, box.size.y, -1, text.c_str(), mousePos.x, mousePos.y);
}
void setText(std::string text);
virtual int getTextPosition(math::Vec mousePos);
};




+ 0
- 1
src/app/Knob.cpp View File

@@ -6,7 +6,6 @@ namespace rack {

void Knob::onDragStart(event::DragStart &e) {
context()->window->cursorLock();
e.target = this;
}

void Knob::onDragEnd(event::DragEnd &e) {


+ 0
- 1
src/app/ModuleBrowser.cpp View File

@@ -12,7 +12,6 @@
#include "ui/TextField.hpp"
#include "plugin.hpp"
#include "context.hpp"
#include "logger.hpp"


static const float itemMargin = 2.0;


+ 3
- 3
src/app/ModuleWidget.cpp View File

@@ -1,11 +1,11 @@
#include "app/ModuleWidget.hpp"
#include "engine/Engine.hpp"
#include "logger.hpp"
#include "system.hpp"
#include "asset.hpp"
#include "app/Scene.hpp"
#include "helpers.hpp"
#include "context.hpp"
#include "settings.hpp"

#include "osdialog.h"

@@ -255,7 +255,7 @@ void ModuleWidget::draw(NVGcontext *vg) {
Widget::draw(vg);

// Power meter
if (module && context()->engine->powerMeter) {
if (module && settings::powerMeter) {
nvgBeginPath(vg);
nvgRect(vg,
0, box.size.y - 20,
@@ -372,7 +372,7 @@ void ModuleWidget::onDragEnd(event::DragEnd &e) {
}

void ModuleWidget::onDragMove(event::DragMove &e) {
if (!context()->scene->rackWidget->lockModules) {
if (!settings::lockModules) {
math::Rect newBox = box;
newBox.pos = context()->scene->rackWidget->lastMousePos.minus(dragPos);
context()->scene->rackWidget->requestModuleBoxNearest(this, newBox);


+ 8
- 0
src/app/ParamQuantity.cpp View File

@@ -41,6 +41,10 @@ float ParamQuantity::getDisplayValue() {
// Linear
return getParam()->value * getParam()->displayMultiplier;
}
else if (getParam()->displayBase == 1.f) {
// Fixed (special case of exponential)
return getParam()->displayMultiplier;
}
else {
// Exponential
return std::pow(getParam()->displayBase, getParam()->value) * getParam()->displayMultiplier;
@@ -52,6 +56,10 @@ void ParamQuantity::setDisplayValue(float displayValue) {
// Linear
getParam()->value = displayValue / getParam()->displayMultiplier;
}
else if (getParam()->displayBase == 1.f) {
// Fixed
getParam()->value = getParam()->displayMultiplier;
}
else {
// Exponential
getParam()->value = std::log(displayValue / getParam()->displayMultiplier) / std::log(getParam()->displayBase);


+ 0
- 241
src/app/PluginManagerWidget.cpp View File

@@ -1,241 +0,0 @@
#include <thread>
#include "system.hpp"
#include "app/PluginManagerWidget.hpp"
#include "ui/SequentialLayout.hpp"
#include "ui/Button.hpp"
#include "ui/ProgressBar.hpp"
#include "ui/TextField.hpp"
#include "ui/PasswordField.hpp"
#include "ui/Label.hpp"
#include "plugin.hpp"
#include "context.hpp"
#include "window.hpp"
#include "helpers.hpp"
#include "osdialog.h"


namespace rack {


struct RegisterButton : Button {
void onAction(event::Action &e) override {
std::thread t([&]() {
system::openBrowser("https://vcvrack.com/");
});
t.detach();
}
};


struct LogInButton : Button {
TextField *emailField;
TextField *passwordField;
void onAction(event::Action &e) override {
std::thread t([&]() {
plugin::logIn(emailField->text, passwordField->text);
});
t.detach();
passwordField->text = "";
}
};


struct StatusLabel : Label {
void step() override {
text = plugin::loginStatus;
}
};


struct ManageButton : Button {
void onAction(event::Action &e) override {
std::thread t([&]() {
system::openBrowser("https://vcvrack.com/plugins.html");
});
t.detach();
}
};


struct SyncButton : Button {
bool checked = false;
/** Updates are available */
bool available = false;
/** Plugins have been updated */
bool completed = false;

void step() override {
// Check for plugin update on first step()
if (!checked) {
std::thread t([this]() {
if (plugin::sync(true))
available = true;
});
t.detach();
checked = true;
}
// Display message if we've completed updates
if (completed) {
if (osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, "All plugins have been updated. Close Rack and re-launch it to load new updates.")) {
context()->window->close();
}
completed = false;
}
}
void draw(NVGcontext *vg) override {
Button::draw(vg);
if (available) {
// Notification circle
nvgBeginPath(vg);
nvgCircle(vg, 3, 3, 4.0);
nvgFillColor(vg, nvgRGBf(1.0, 0.0, 0.0));
nvgFill(vg);
nvgStrokeColor(vg, nvgRGBf(0.5, 0.0, 0.0));
nvgStroke(vg);
}
}
void onAction(event::Action &e) override {
available = false;
std::thread t([this]() {
if (plugin::sync(false))
completed = true;
});
t.detach();
}
};


struct LogOutButton : Button {
void onAction(event::Action &e) override {
plugin::logOut();
}
};


struct DownloadQuantity : Quantity {
float getValue() override {
return plugin::downloadProgress;
}

float getDisplayValue() override {
return getValue() * 100.f;
}

int getDisplayPrecision() override {return 0;}

std::string getLabel() override {
return "Downloading " + plugin::downloadName;
}

std::string getUnit() override {return "%";}
};


struct DownloadProgressBar : ProgressBar {
DownloadProgressBar() {
quantity = new DownloadQuantity;
}
};


struct CancelButton : Button {
void onAction(event::Action &e) override {
plugin::cancelDownload();
}
};


PluginManagerWidget::PluginManagerWidget() {
box.size.y = BND_WIDGET_HEIGHT;

{
SequentialLayout *layout = createWidget<SequentialLayout>(math::Vec(0, 0));
layout->spacing = 5;
loginWidget = layout;

Button *registerButton = new RegisterButton;
registerButton->box.size.x = 75;
registerButton->text = "Register";
loginWidget->addChild(registerButton);

TextField *emailField = new TextField;
emailField->box.size.x = 175;
emailField->placeholder = "Email";
loginWidget->addChild(emailField);

PasswordField *passwordField = new PasswordField;
passwordField->box.size.x = 175;
passwordField->placeholder = "Password";
loginWidget->addChild(passwordField);

LogInButton *logInButton = new LogInButton;
logInButton->box.size.x = 100;
logInButton->text = "Log in";
logInButton->emailField = emailField;
logInButton->passwordField = passwordField;
loginWidget->addChild(logInButton);

Label *label = new StatusLabel;
loginWidget->addChild(label);

addChild(loginWidget);
}

{
SequentialLayout *layout = createWidget<SequentialLayout>(math::Vec(0, 0));
layout->spacing = 5;
manageWidget = layout;

Button *manageButton = new ManageButton;
manageButton->box.size.x = 125;
manageButton->text = "Manage plugins";
manageWidget->addChild(manageButton);

Button *syncButton = new SyncButton;
syncButton->box.size.x = 125;
syncButton->text = "Update plugins";
manageWidget->addChild(syncButton);

Button *logOutButton = new LogOutButton;
logOutButton->box.size.x = 100;
logOutButton->text = "Log out";
manageWidget->addChild(logOutButton);

addChild(manageWidget);
}

{
SequentialLayout *layout = createWidget<SequentialLayout>(math::Vec(0, 0));
layout->spacing = 5;
downloadWidget = layout;

ProgressBar *downloadProgress = new DownloadProgressBar;
downloadProgress->box.size.x = 300;
downloadWidget->addChild(downloadProgress);

// Button *cancelButton = new CancelButton;
// cancelButton->box.size.x = 100;
// cancelButton->text = "Cancel";
// downloadWidget->addChild(cancelButton);

addChild(downloadWidget);
}
}

void PluginManagerWidget::step() {
loginWidget->visible = false;
manageWidget->visible = false;
downloadWidget->visible = false;

if (plugin::isDownloading)
downloadWidget->visible = true;
else if (plugin::isLoggedIn())
manageWidget->visible = true;
else
loginWidget->visible = true;

Widget::step();
}


} // namespace rack

+ 0
- 2
src/app/RackWidget.cpp View File

@@ -8,7 +8,6 @@
#include "settings.hpp"
#include "asset.hpp"
#include "system.hpp"
#include "logger.hpp"
#include "plugin.hpp"
#include "context.hpp"

@@ -511,7 +510,6 @@ void RackWidget::onHover(event::Hover &e) {
}

void RackWidget::onButton(event::Button &e) {
DEBUG("what");
OpaqueWidget::onButton(e);
if (e.target == this) {
if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) {


+ 0
- 1
src/app/Scene.cpp View File

@@ -5,7 +5,6 @@
#include "app/ModuleBrowser.hpp"
#include "app/RackScrollWidget.hpp"
#include "context.hpp"
#include "logger.hpp"
#include <thread>




+ 415
- 134
src/app/Toolbar.cpp View File

@@ -2,250 +2,531 @@
#include "window.hpp"
#include "engine/Engine.hpp"
#include "asset.hpp"
#include "ui/Tooltip.hpp"
#include "ui/IconButton.hpp"
#include "ui/Button.hpp"
#include "ui/MenuItem.hpp"
#include "ui/SequentialLayout.hpp"
#include "ui/Slider.hpp"
#include "app/PluginManagerWidget.hpp"
#include "ui/TextField.hpp"
#include "ui/PasswordField.hpp"
#include "ui/ProgressBar.hpp"
#include "app/Scene.hpp"
#include "context.hpp"
#include "settings.hpp"
#include "helpers.hpp"
#include "system.hpp"
#include "plugin.hpp"
#include <thread>


namespace rack {


struct TooltipIconButton : IconButton {
Tooltip *tooltip = NULL;
void onEnter(event::Enter &e) override {
if (!tooltip) {
tooltip = new Tooltip;
tooltip->box.pos = getAbsoluteOffset(math::Vec(0, BND_WIDGET_HEIGHT));
tooltip->text = getTooltipText();
context()->scene->addChild(tooltip);
}
IconButton::onEnter(e);
struct MenuButton : Button {
void step() override {
box.size.x = bndLabelWidth(context()->window->vg, -1, text.c_str());
Widget::step();
}
void onLeave(event::Leave &e) override {
if (tooltip) {
context()->scene->removeChild(tooltip);
delete tooltip;
tooltip = NULL;
}
IconButton::onLeave(e);
void draw(NVGcontext *vg) override {
bndMenuItem(vg, 0.0, 0.0, box.size.x, box.size.y, state, -1, text.c_str());
}
virtual std::string getTooltipText() {return "";}
};

struct NewButton : TooltipIconButton {
NewButton() {
setSVG(SVG::load(asset::system("res/icons/noun_146097_cc.svg")));

struct NewItem : MenuItem {
NewItem() {
text = "New";
rightText = "(" WINDOW_MOD_KEY_NAME "+N)";
}
std::string getTooltipText() override {return "New patch (" WINDOW_MOD_KEY_NAME "+N)";}
void onAction(event::Action &e) override {
context()->scene->rackWidget->reset();
}
};

struct OpenButton : TooltipIconButton {
OpenButton() {
setSVG(SVG::load(asset::system("res/icons/noun_31859_cc.svg")));

struct OpenItem : MenuItem {
OpenItem() {
text = "Open";
rightText = "(" WINDOW_MOD_KEY_NAME "+O)";
}
std::string getTooltipText() override {return "Open patch (" WINDOW_MOD_KEY_NAME "+O)";}
void onAction(event::Action &e) override {
context()->scene->rackWidget->loadDialog();
}
};

struct SaveButton : TooltipIconButton {
SaveButton() {
setSVG(SVG::load(asset::system("res/icons/noun_1343816_cc.svg")));

struct SaveItem : MenuItem {
SaveItem() {
text = "Save";
rightText = "(" WINDOW_MOD_KEY_NAME "+S)";
}
std::string getTooltipText() override {return "Save patch (" WINDOW_MOD_KEY_NAME "+S)";}
void onAction(event::Action &e) override {
context()->scene->rackWidget->saveDialog();
}
};

struct SaveAsButton : TooltipIconButton {
SaveAsButton() {
setSVG(SVG::load(asset::system("res/icons/noun_1343811_cc.svg")));

struct SaveAsItem : MenuItem {
SaveAsItem() {
text = "Save as";
rightText = "(" WINDOW_MOD_KEY_NAME "+Shift+S)";
}
std::string getTooltipText() override {return "Save patch as (" WINDOW_MOD_KEY_NAME "+Shift+S)";}
void onAction(event::Action &e) override {
context()->scene->rackWidget->saveAsDialog();
}
};

struct RevertButton : TooltipIconButton {
RevertButton() {
setSVG(SVG::load(asset::system("res/icons/noun_1084369_cc.svg")));

struct RevertItem : MenuItem {
RevertItem() {
text = "Revert";
}
std::string getTooltipText() override {return "Revert patch";}
void onAction(event::Action &e) override {
context()->scene->rackWidget->revert();
}
};

struct DisconnectCablesButton : TooltipIconButton {
DisconnectCablesButton() {
setSVG(SVG::load(asset::system("res/icons/noun_1745061_cc.svg")));

struct DisconnectCablesItem : MenuItem {
DisconnectCablesItem() {
text = "Disconnect cables";
}
std::string getTooltipText() override {return "Disconnect cables";}
void onAction(event::Action &e) override {
context()->scene->rackWidget->disconnect();
}
};

struct PowerMeterButton : TooltipIconButton {
PowerMeterButton() {
setSVG(SVG::load(asset::system("res/icons/noun_305536_cc.svg")));

struct FileButton : MenuButton {
FileButton() {
text = "File";
}
std::string getTooltipText() override {return "Toggle power meter (see manual for explanation)";}
void onAction(event::Action &e) override {
context()->engine->powerMeter ^= true;
Menu *menu = createMenu();
menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
menu->box.size.x = box.size.x;

menu->addChild(new NewItem);
menu->addChild(new OpenItem);
menu->addChild(new SaveItem);
menu->addChild(new SaveAsItem);
menu->addChild(new RevertItem);
menu->addChild(new DisconnectCablesItem);
}
};


struct ZoomQuantity : Quantity {
void setValue(float value) override {
settings::zoom = math::clamp(value, getMinValue(), getMaxValue());
}
float getValue() override {
return settings::zoom;
}
float getMinValue() override {return 0.25;}
float getMaxValue() override {return 2.0;}
float getDefaultValue() override {return 1.0;}
float getDisplayValue() override {return getValue() * 100.0;}
void setDisplayValue(float displayValue) override {setValue(displayValue / 100.0);}
std::string getLabel() override {return "Zoom";}
std::string getUnit() override {return "%";}
int getDisplayPrecision() override {return 0;}
};


struct WireOpacityQuantity : Quantity {
void setValue(float value) override {
settings::wireOpacity = math::clamp(value, getMinValue(), getMaxValue());
}
float getValue() override {
return settings::wireOpacity;
}
float getDefaultValue() override {return 0.5;}
float getDisplayValue() override {return getValue() * 100.0;}
void setDisplayValue(float displayValue) override {setValue(displayValue / 100.0);}
std::string getLabel() override {return "Cable opacity";}
std::string getUnit() override {return "%";}
int getDisplayPrecision() override {return 0;}
};



struct WireTensionQuantity : Quantity {
void setValue(float value) override {
settings::wireTension = math::clamp(value, getMinValue(), getMaxValue());
}
float getValue() override {
return settings::wireTension;
}
float getDefaultValue() override {return 0.5;}
std::string getLabel() override {return "Cable tension";}
int getDisplayPrecision() override {return 2;}
};


struct PowerMeterItem : MenuItem {
PowerMeterItem() {
text = "Power meter";
rightText = CHECKMARK(settings::powerMeter);
}
void onAction(event::Action &e) override {
settings::powerMeter ^= true;
}
};


struct LockModulesItem : MenuItem {
LockModulesItem() {
text = "Lock modules";
rightText = CHECKMARK(settings::lockModules);
}
void onAction(event::Action &e) override {
settings::lockModules ^= true;
}
};


struct EnginePauseItem : MenuItem {
EnginePauseItem() {
text = "Pause engine";
rightText = CHECKMARK(context()->engine->paused);
}
void onAction(event::Action &e) override {
context()->engine->paused ^= true;
}
};

struct SampleRateItem : MenuItem {

struct SampleRateValueItem : MenuItem {
float sampleRate;
SampleRateValueItem(float sampleRate) {
this->sampleRate = sampleRate;
text = string::f("%.0f Hz", sampleRate);
rightText = CHECKMARK(context()->engine->getSampleRate() == sampleRate);
}
void onAction(event::Action &e) override {
context()->engine->setSampleRate(sampleRate);
context()->engine->paused = false;
}
};

struct SampleRateButton : TooltipIconButton {
SampleRateButton() {
setSVG(SVG::load(asset::system("res/icons/noun_1240789_cc.svg")));

struct SampleRateItem : MenuItem {
SampleRateItem() {
text = "Engine sample rate";
}
Menu *createChildMenu() override {
Menu *menu = new Menu;

menu->addChild(new EnginePauseItem);

std::vector<float> sampleRates = {44100, 48000, 88200, 96000, 176400, 192000};
for (float sampleRate : sampleRates) {
menu->addChild(new SampleRateValueItem(sampleRate));
}
return menu;
}
};


struct SettingsButton : MenuButton {
SettingsButton() {
text = "Settings";
}
std::string getTooltipText() override {return "Engine sample rate";}
void onAction(event::Action &e) override {
Menu *menu = createMenu();
menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
menu->box.size.x = box.size.x;

menu->addChild(createMenuLabel("Engine sample rate"));
menu->addChild(new PowerMeterItem);
menu->addChild(new LockModulesItem);
menu->addChild(new SampleRateItem);

EnginePauseItem *pauseItem = new EnginePauseItem;
pauseItem->text = context()->engine->paused ? "Resume engine" : "Pause engine";
menu->addChild(pauseItem);
Slider *zoomSlider = new Slider;
zoomSlider->box.size.x = 200.0;
zoomSlider->quantity = new ZoomQuantity;
menu->addChild(zoomSlider);

std::vector<float> sampleRates = {44100, 48000, 88200, 96000, 176400, 192000};
for (float sampleRate : sampleRates) {
SampleRateItem *item = new SampleRateItem;
item->text = string::f("%.0f Hz", sampleRate);
item->rightText = CHECKMARK(context()->engine->getSampleRate() == sampleRate);
item->sampleRate = sampleRate;
menu->addChild(item);
Slider *wireOpacitySlider = new Slider;
wireOpacitySlider->box.size.x = 200.0;
wireOpacitySlider->quantity = new WireOpacityQuantity;
menu->addChild(wireOpacitySlider);

Slider *wireTensionSlider = new Slider;
wireTensionSlider->box.size.x = 200.0;
wireTensionSlider->quantity = new WireTensionQuantity;
menu->addChild(wireTensionSlider);
}
};


struct RegisterItem : MenuItem {
RegisterItem() {
text = "Register VCV account";
}
void onAction(event::Action &e) override {
std::thread t([&]() {
system::openBrowser("https://vcvrack.com/");
});
t.detach();
}
};


struct AccountEmailField : TextField {
TextField *passwordField;
AccountEmailField() {
placeholder = "Email";
}
void onSelectKey(event::SelectKey &e) override {
if (e.action == GLFW_PRESS && e.key == GLFW_KEY_TAB) {
context()->event->selectedWidget = passwordField;
e.target = this;
return;
}
TextField::onSelectKey(e);
}
};

struct RackLockButton : TooltipIconButton {
RackLockButton() {
setSVG(SVG::load(asset::system("res/icons/noun_468341_cc.svg")));

struct AccountPasswordField : PasswordField {
MenuItem *logInItem;
AccountPasswordField() {
placeholder = "Password";
}
void onSelectKey(event::SelectKey &e) override {
if (e.action == GLFW_PRESS && (e.key == GLFW_KEY_ENTER || e.key == GLFW_KEY_KP_ENTER)) {
logInItem->doAction();
e.target = this;
return;
}
PasswordField::onSelectKey(e);
}
};


struct LogInItem : MenuItem {
TextField *emailField;
TextField *passwordField;
LogInItem() {
text = "Log in";
}
std::string getTooltipText() override {return "Lock modules";}
void onAction(event::Action &e) override {
context()->scene->rackWidget->lockModules ^= true;
std::string email = emailField->text;
std::string password = passwordField->text;
std::thread t([&, email, password]() {
plugin::logIn(email, password);
});
t.detach();
}
};

struct WireOpacityQuantity : Quantity {
void setValue(float value) override {
// TODO

struct ManageItem : MenuItem {
ManageItem() {
text = "Manage plugins";
}
float getValue() override {
return 0;
void onAction(event::Action &e) override {
std::thread t([&]() {
system::openBrowser("https://vcvrack.com/plugins.html");
});
t.detach();
}
float getDefaultValue() override {return 0.5;}
std::string getLabel() override {return "Cable opacity";}
int getDisplayPrecision() override {return 0;}
};


struct WireTensionQuantity : Quantity {
void setValue(float value) override {
// TODO
// struct SyncButton : Button {
// bool checked = false;
// /** Updates are available */
// bool available = false;
// /** Plugins have been updated */
// bool completed = false;

// void step() override {
// // Check for plugin update on first step()
// if (!checked) {
// std::thread t([this]() {
// if (plugin::sync(true))
// available = true;
// });
// t.detach();
// checked = true;
// }
// // Display message if we've completed updates
// if (completed) {
// if (osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, "All plugins have been updated. Close Rack and re-launch it to load new updates.")) {
// context()->window->close();
// }
// completed = false;
// }
// }
// void onAction(event::Action &e) override {
// available = false;
// std::thread t([this]() {
// if (plugin::sync(false))
// completed = true;
// });
// t.detach();
// }
// };


struct LogOutItem : MenuItem {
LogOutItem() {
text = "Log out";
}
void onAction(event::Action &e) override {
plugin::logOut();
}
};


struct DownloadQuantity : Quantity {
float getValue() override {
return 0;
return plugin::downloadProgress;
}
float getDefaultValue() override {return 0.5;}
std::string getLabel() override {return "Cable tension";}

float getDisplayValue() override {
return getValue() * 100.f;
}

int getDisplayPrecision() override {return 0;}

std::string getLabel() override {
return "Downloading " + plugin::downloadName;
}

std::string getUnit() override {return "%";}
};


struct ZoomQuantity : Quantity {
void setValue(float value) override {
context()->scene->zoomWidget->setZoom(std::round(value) / 100);
struct PluginsButton : MenuButton {
PluginsButton() {
text = "Plugins";
}
float getValue() override {
return context()->scene->zoomWidget->zoom * 100;
void onAction(event::Action &e) override {
Menu *menu = createMenu();
menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
menu->box.size.x = box.size.x;

// TODO Design dialog box for plugin syncing
if (plugin::isDownloading) {
ProgressBar *downloadProgressBar = new ProgressBar;
downloadProgressBar->quantity = new DownloadQuantity;
menu->addChild(downloadProgressBar);
}
else if (plugin::isLoggedIn()) {
menu->addChild(new ManageItem);
menu->addChild(new LogOutItem);
}
else {
menu->addChild(new RegisterItem);
AccountEmailField *emailField = new AccountEmailField;
emailField->box.size.x = 200.0;
menu->addChild(emailField);
AccountPasswordField *passwordField = new AccountPasswordField;
passwordField->box.size.x = 200.0;
emailField->passwordField = passwordField;
menu->addChild(passwordField);
LogInItem *logInItem = new LogInItem;
logInItem->emailField = emailField;
logInItem->passwordField = passwordField;
passwordField->logInItem = logInItem;
menu->addChild(logInItem);
}
}

void draw(NVGcontext *vg) override {
MenuButton::draw(vg);
// if (1) {
// // Notification circle
// nvgBeginPath(vg);
// nvgCircle(vg, box.size.x - 3, 3, 4.0);
// nvgFillColor(vg, nvgRGBf(1.0, 0.0, 0.0));
// nvgFill(vg);
// nvgStrokeColor(vg, nvgRGBf(0.5, 0.0, 0.0));
// nvgStroke(vg);
// }
}
};


struct ManualItem : MenuItem {
ManualItem() {
text = "Manual";
}
void onAction(event::Action &e) override {
std::thread t([&]() {
system::openBrowser("https://vcvrack.com/manual/");
});
t.detach();
}
};


struct WebsiteItem : MenuItem {
WebsiteItem() {
text = "VCVRack.com";
}
void onAction(event::Action &e) override {
std::thread t([&]() {
system::openBrowser("https://vcvrack.com/");
});
t.detach();
}
};


struct CheckVersionItem : MenuItem {
CheckVersionItem() {
text = "Check version on launch";
rightText = CHECKMARK(settings::checkVersion);
}
void onAction(event::Action &e) override {
settings::checkVersion ^= true;
}
};


struct HelpButton : MenuButton {
HelpButton() {
text = "Help";
}
void onAction(event::Action &e) override {
Menu *menu = createMenu();
menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
menu->box.size.x = box.size.x;

menu->addChild(new ManualItem);
menu->addChild(new WebsiteItem);
menu->addChild(new CheckVersionItem);
}
float getMinValue() override {return 25;}
float getMaxValue() override {return 200;}
float getDefaultValue() override {return 100;}
std::string getLabel() override {return "Zoom";}
std::string getUnit() override {return "%";}
int getDisplayPrecision() override {return 0;}
};


Toolbar::Toolbar() {
box.size.y = BND_WIDGET_HEIGHT + 2*5;
const float margin = 5;
box.size.y = BND_WIDGET_HEIGHT + 2*margin;

SequentialLayout *layout = new SequentialLayout;
layout->box.pos = math::Vec(5, 5);
layout->spacing = 5;
layout->box.pos = math::Vec(margin, margin);
layout->spacing = 0.0;
addChild(layout);

layout->addChild(new NewButton);
layout->addChild(new OpenButton);
layout->addChild(new SaveButton);
layout->addChild(new SaveAsButton);
layout->addChild(new RevertButton);
layout->addChild(new DisconnectCablesButton);

layout->addChild(new SampleRateButton);
layout->addChild(new PowerMeterButton);
layout->addChild(new RackLockButton);

Slider *wireOpacitySlider = new Slider;
WireOpacityQuantity *wireOpacityQuantity = new WireOpacityQuantity;
wireOpacitySlider->quantity = wireOpacityQuantity;
wireOpacitySlider->box.size.x = 150;
layout->addChild(wireOpacitySlider);

Slider *wireTensionSlider = new Slider;
WireTensionQuantity *wireTensionQuantity = new WireTensionQuantity;
wireTensionSlider->quantity = wireTensionQuantity;
wireTensionSlider->box.size.x = 150;
layout->addChild(wireTensionSlider);

Slider *zoomSlider = new Slider;
ZoomQuantity *zoomQuantity = new ZoomQuantity;
zoomSlider->quantity = zoomQuantity;
zoomSlider->box.size.x = 150;
layout->addChild(zoomSlider);

// Kind of hacky, but display the PluginManagerWidget only if the user directory is not the development directory
if (asset::user("") != "./") {
Widget *pluginManager = new PluginManagerWidget;
layout->addChild(pluginManager);
}
FileButton *fileButton = new FileButton;
layout->addChild(fileButton);

SettingsButton *settingsButton = new SettingsButton;
layout->addChild(settingsButton);

PluginsButton *pluginsButton = new PluginsButton;
layout->addChild(pluginsButton);

HelpButton *helpButton = new HelpButton;
layout->addChild(helpButton);
}

void Toolbar::draw(NVGcontext *vg) {
bndBackground(vg, 0.0, 0.0, box.size.x, box.size.y);
bndMenuBackground(vg, 0.0, 0.0, box.size.x, box.size.y, 0);
bndBevel(vg, 0.0, 0.0, box.size.x, box.size.y);

Widget::draw(vg);


+ 3
- 4
src/app/WireWidget.cpp View File

@@ -5,6 +5,7 @@
#include "window.hpp"
#include "event.hpp"
#include "context.hpp"
#include "settings.hpp"


namespace rack {
@@ -162,10 +163,8 @@ void WireWidget::fromJson(json_t *rootJ) {
}

void WireWidget::draw(NVGcontext *vg) {
// float opacity = gToolbar->wireOpacitySlider->value / 100.0;
// float tension = gToolbar->wireTensionSlider->value;
float opacity = 0.5;
float tension = 0.5;
float opacity = settings::wireOpacity;
float tension = settings::wireTension;

WireWidget *activeWire = context()->scene->rackWidget->wireContainer->activeWire;
if (activeWire) {


+ 0
- 1
src/audio.cpp View File

@@ -1,5 +1,4 @@
#include "audio.hpp"
#include "logger.hpp"
#include "string.hpp"
#include "math.hpp"
#include "bridge.hpp"


+ 0
- 1
src/bridge.cpp View File

@@ -1,6 +1,5 @@
#include "bridge.hpp"
#include "string.hpp"
#include "logger.hpp"
#include "dsp/ringbuffer.hpp"

#include <unistd.h>


+ 2
- 1
src/engine/Engine.cpp View File

@@ -1,4 +1,5 @@
#include "engine/Engine.hpp"
#include "settings.hpp"

#include <algorithm>
#include <chrono>
@@ -125,7 +126,7 @@ static void Engine_step(Engine *engine) {

// Step modules
for (Module *module : engine->modules) {
if (engine->powerMeter) {
if (settings::powerMeter) {
auto startTime = std::chrono::high_resolution_clock::now();

module->step();


+ 96
- 79
src/event.cpp View File

@@ -1,12 +1,95 @@
#include "event.hpp"
#include "widgets/Widget.hpp"
#include "logger.hpp"


namespace rack {
namespace event {


void Context::setHovered(Widget *w) {
if (w == hoveredWidget)
return;

if (hoveredWidget) {
// event::Leave
event::Leave eLeave;
hoveredWidget->onLeave(eLeave);
}

hoveredWidget = w;

if (hoveredWidget) {
// event::Enter
event::Enter eEnter;
hoveredWidget->onEnter(eEnter);
}
}

void Context::setDragged(Widget *w) {
if (w == draggedWidget)
return;

if (draggedWidget) {
// event::DragEnd
event::DragEnd eDragEnd;
draggedWidget->onDragEnd(eDragEnd);
}

draggedWidget = w;

if (draggedWidget) {
// event::DragStart
event::DragStart eDragStart;
draggedWidget->onDragStart(eDragStart);
}
}

void Context::setDragHovered(Widget *w) {
if (w == dragHoveredWidget)
return;

if (dragHoveredWidget) {
// event::DragLeave
event::DragLeave eDragLeave;
dragHoveredWidget->onDragLeave(eDragLeave);
}

dragHoveredWidget = w;

if (dragHoveredWidget) {
// event::DragEnter
event::DragEnter eDragEnter;
dragHoveredWidget->onDragEnter(eDragEnter);
}
}

void Context::setSelected(Widget *w) {
if (w == selectedWidget)
return;

if (selectedWidget) {
// event::Deselect
event::Deselect eDeselect;
selectedWidget->onDeselect(eDeselect);
}

selectedWidget = w;

if (selectedWidget) {
// event::Select
event::Select eSelect;
selectedWidget->onSelect(eSelect);
}
}

void Context::finalizeWidget(Widget *w) {
if (hoveredWidget == w) setHovered(NULL);
if (draggedWidget == w) setDragged(NULL);
if (dragHoveredWidget == w) setDragHovered(NULL);
if (selectedWidget == w) setSelected(NULL);
if (scrollWidget == w) scrollWidget = NULL;
}

void Context::handleButton(math::Vec pos, int button, int action, int mods) {
// event::Button
event::Button eButton;
@@ -18,48 +101,25 @@ void Context::handleButton(math::Vec pos, int button, int action, int mods) {
Widget *clickedWidget = eButton.target;

if (button == GLFW_MOUSE_BUTTON_LEFT) {
if (action == GLFW_PRESS && !draggedWidget && clickedWidget) {
// event::DragStart
event::DragStart eDragStart;
clickedWidget->onDragStart(eDragStart);
draggedWidget = eDragStart.target;
if (action == GLFW_PRESS) {
setDragged(clickedWidget);
}

if (action == GLFW_RELEASE && draggedWidget) {
if (dragHoveredWidget) {
// event::DragLeave
event::DragLeave eDragLeave;
dragHoveredWidget->onDragLeave(eDragLeave);
}
if (action == GLFW_RELEASE) {
setDragHovered(NULL);

if (clickedWidget) {
if (clickedWidget && draggedWidget) {
// event::DragDrop
event::DragDrop eDragDrop;
eDragDrop.origin = draggedWidget;
clickedWidget->onDragDrop(eDragDrop);
}

// event::DragEnd
event::DragEnd eDragEnd;
draggedWidget->onDragEnd(eDragEnd);
draggedWidget = NULL;
dragHoveredWidget = NULL;
setDragged(NULL);
}

if (action == GLFW_PRESS && clickedWidget != selectedWidget) {
if (selectedWidget) {
// event::Deselect
event::Deselect eDeselect;
selectedWidget->onDeselect(eDeselect);
}

selectedWidget = clickedWidget;

if (selectedWidget) {
// event::Select
event::Select eSelect;
selectedWidget->onSelect(eSelect);
}
if (action == GLFW_PRESS) {
setSelected(clickedWidget);
}
}

@@ -73,7 +133,6 @@ void Context::handleButton(math::Vec pos, int button, int action, int mods) {
// }
}


void Context::handleHover(math::Vec pos, math::Vec mouseDelta) {
if (draggedWidget) {
// event::DragMove
@@ -86,23 +145,8 @@ void Context::handleHover(math::Vec pos, math::Vec mouseDelta) {
eDragHover.pos = pos;
eDragHover.mouseDelta = mouseDelta;
rootWidget->onDragHover(eDragHover);
Widget *newDragHoveredWidget = eDragHover.target;

if (newDragHoveredWidget != dragHoveredWidget) {
if (dragHoveredWidget) {
// event::DragLeave
event::DragLeave eDragLeave;
dragHoveredWidget->onDragLeave(eDragLeave);
}

dragHoveredWidget = newDragHoveredWidget;

if (dragHoveredWidget) {
// event::DragEnter
event::DragEnter eDragEnter;
dragHoveredWidget->onDragEnter(eDragEnter);
}
}
setDragHovered(eDragHover.target);

return;
}
@@ -120,32 +164,13 @@ void Context::handleHover(math::Vec pos, math::Vec mouseDelta) {
eHover.pos = pos;
eHover.mouseDelta = mouseDelta;
rootWidget->onHover(eHover);
Widget *newHoveredWidget = eHover.target;

if (newHoveredWidget != hoveredWidget) {
if (hoveredWidget) {
// event::Leave
event::Leave eLeave;
hoveredWidget->onLeave(eLeave);
}

hoveredWidget = newHoveredWidget;

if (hoveredWidget) {
// event::Enter
event::Enter eEnter;
hoveredWidget->onEnter(eEnter);
}
}
setHovered(eHover.target);
}

void Context::handleLeave() {
if (hoveredWidget) {
// event::Leave
event::Leave eLeave;
hoveredWidget->onLeave(eLeave);
}
hoveredWidget = NULL;
setDragHovered(NULL);
setHovered(NULL);
}

void Context::handleScroll(math::Vec pos, math::Vec scrollDelta) {
@@ -204,14 +229,6 @@ void Context::handleKey(math::Vec pos, int key, int scancode, int action, int mo
rootWidget->onHoverKey(eHoverKey);
}

void Context::finalizeWidget(Widget *w) {
if (hoveredWidget == w) hoveredWidget = NULL;
if (draggedWidget == w) draggedWidget = NULL;
if (dragHoveredWidget == w) dragHoveredWidget = NULL;
if (selectedWidget == w) selectedWidget = NULL;
if (scrollWidget == w) scrollWidget = NULL;
}

void Context::handleZoom() {
// event::Zoom
event::Zoom eZoom;


+ 1
- 1
src/logger.cpp View File

@@ -1,4 +1,4 @@
#include "logger.hpp"
#include "common.hpp"
#include "asset.hpp"
#include <chrono>



+ 0
- 1
src/main.cpp View File

@@ -1,6 +1,5 @@
#include "common.hpp"
#include "random.hpp"
#include "logger.hpp"
#include "asset.hpp"
#include "rtmidi.hpp"
#include "keyboard.hpp"


+ 0
- 1
src/plugin.cpp View File

@@ -1,6 +1,5 @@
#include "plugin.hpp"
#include "system.hpp"
#include "logger.hpp"
#include "network.hpp"
#include "asset.hpp"
#include "string.hpp"


+ 0
- 1
src/plugin/Model.cpp View File

@@ -1,5 +1,4 @@
#include "plugin/Model.hpp"
#include "logger.hpp"


namespace rack {


+ 0
- 1
src/plugin/Plugin.cpp View File

@@ -1,6 +1,5 @@
#include "plugin/Plugin.hpp"
#include "plugin/Model.hpp"
#include "logger.hpp"


namespace rack {


+ 25
- 22
src/settings.cpp View File

@@ -1,5 +1,4 @@
#include "settings.hpp"
#include "logger.hpp"
#include "window.hpp"
#include "plugin.hpp"
#include "app/Scene.hpp"
@@ -33,18 +32,15 @@ static json_t *settingsToJson() {
json_object_set_new(rootJ, "windowPos", windowPosJ);
}

// opacity
float opacity = context()->scene->toolbar->wireOpacity;
json_t *opacityJ = json_real(opacity);
json_object_set_new(rootJ, "wireOpacity", opacityJ);
// wireOpacity
json_t *wireOpacityJ = json_real(wireOpacity);
json_object_set_new(rootJ, "wireOpacity", wireOpacityJ);

// tension
float tension = context()->scene->toolbar->wireTension;
json_t *tensionJ = json_real(tension);
json_object_set_new(rootJ, "wireTension", tensionJ);
// wireTension
json_t *wireTensionJ = json_real(wireTension);
json_object_set_new(rootJ, "wireTension", wireTensionJ);

// zoom
float zoom = context()->scene->zoomWidget->zoom;
json_t *zoomJ = json_real(zoom);
json_object_set_new(rootJ, "zoom", zoomJ);

@@ -69,10 +65,10 @@ static json_t *settingsToJson() {
json_object_set_new(rootJ, "moduleBrowser", moduleBrowserToJson());

// powerMeter
json_object_set_new(rootJ, "powerMeter", json_boolean(context()->engine->powerMeter));
json_object_set_new(rootJ, "powerMeter", json_boolean(powerMeter));

// checkVersion
json_object_set_new(rootJ, "checkVersion", json_boolean(context()->scene->checkVersion));
json_object_set_new(rootJ, "checkVersion", json_boolean(checkVersion));

return rootJ;
}
@@ -99,21 +95,20 @@ static void settingsFromJson(json_t *rootJ) {
context()->window->setWindowPos(math::Vec(x, y));
}

// opacity
json_t *opacityJ = json_object_get(rootJ, "wireOpacity");
if (opacityJ)
context()->scene->toolbar->wireOpacity = json_number_value(opacityJ);
// wireOpacity
json_t *wireOpacityJ = json_object_get(rootJ, "wireOpacity");
if (wireOpacityJ)
wireOpacity = json_number_value(wireOpacityJ);

// tension
json_t *tensionJ = json_object_get(rootJ, "wireTension");
if (tensionJ)
context()->scene->toolbar->wireTension = json_number_value(tensionJ);
wireTension = json_number_value(tensionJ);

// zoom
json_t *zoomJ = json_object_get(rootJ, "zoom");
if (zoomJ) {
context()->scene->zoomWidget->setZoom(math::clamp((float) json_number_value(zoomJ), 0.25f, 4.0f));
}
if (zoomJ)
zoom = json_number_value(zoomJ);

// allowCursorLock
json_t *allowCursorLockJ = json_object_get(rootJ, "allowCursorLock");
@@ -145,12 +140,12 @@ static void settingsFromJson(json_t *rootJ) {
// powerMeter
json_t *powerMeterJ = json_object_get(rootJ, "powerMeter");
if (powerMeterJ)
context()->engine->powerMeter = json_boolean_value(powerMeterJ);
powerMeter = json_boolean_value(powerMeterJ);

// checkVersion
json_t *checkVersionJ = json_object_get(rootJ, "checkVersion");
if (checkVersionJ)
context()->scene->checkVersion = json_boolean_value(checkVersionJ);
checkVersion = json_boolean_value(checkVersionJ);
}


@@ -188,5 +183,13 @@ void load(std::string filename) {
}


float zoom = 1.0;
float wireOpacity = 0.5;
float wireTension = 0.5;
bool powerMeter = false;
bool lockModules = false;
bool checkVersion = true;


} // namespace settings
} // namespace rack

+ 62
- 0
src/ui/Menu.cpp View File

@@ -0,0 +1,62 @@
#include "ui/Menu.hpp"

namespace rack {


Menu::Menu() {
box.size = math::Vec(0, 0);
}

Menu::~Menu() {
setChildMenu(NULL);
}

void Menu::setChildMenu(Menu *menu) {
if (childMenu) {
if (childMenu->parent)
childMenu->parent->removeChild(childMenu);
delete childMenu;
childMenu = NULL;
}
if (menu) {
childMenu = menu;
assert(parent);
parent->addChild(childMenu);
}
}

void Menu::step() {
Widget::step();

// Set positions of children
box.size = math::Vec(0, 0);
for (Widget *child : children) {
if (!child->visible)
continue;
// Increment height, set position of child
child->box.pos = math::Vec(0, box.size.y);
box.size.y += child->box.size.y;
// Increase width based on maximum width of child
if (child->box.size.x > box.size.x) {
box.size.x = child->box.size.x;
}
}

// Set widths of all children to maximum width
for (Widget *child : children) {
child->box.size.x = box.size.x;
}
}

void Menu::draw(NVGcontext *vg) {
bndMenuBackground(vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_NONE);
Widget::draw(vg);
}

void Menu::onHoverScroll(event::HoverScroll &e) {
if (parent && !parent->box.contains(box))
box.pos.y += e.scrollDelta.y;
}


} // namespace rack

+ 69
- 0
src/ui/MenuItem.cpp View File

@@ -0,0 +1,69 @@
#include "ui/MenuItem.hpp"

namespace rack {


void MenuItem::draw(NVGcontext *vg) {
// Get state
BNDwidgetState state = (context()->event->hoveredWidget == this) ? BND_HOVER : BND_DEFAULT;
// Set active state if this MenuItem
Menu *parentMenu = dynamic_cast<Menu*>(parent);
if (parentMenu && parentMenu->activeEntry == this) {
state = BND_ACTIVE;
}

bndMenuItem(vg, 0.0, 0.0, box.size.x, box.size.y, state, -1, text.c_str());

float x = box.size.x - bndLabelWidth(vg, -1, rightText.c_str());
NVGcolor rightColor = (state == BND_DEFAULT) ? bndGetTheme()->menuTheme.textColor : bndGetTheme()->menuTheme.textSelectedColor;
bndIconLabelValue(vg, x, 0.0, box.size.x, box.size.y, -1, rightColor, BND_LEFT, BND_LABEL_FONT_SIZE, rightText.c_str(), NULL);
}

void MenuItem::step() {
// Add 10 more pixels because measurements on high-DPI screens are sometimes too small for some reason
const float rightPadding = 10.0;
// HACK use context()->window->vg from the window.
// All this does is inspect the font, so it shouldn't modify context()->window->vg and should work when called from a FramebufferWidget for example.
box.size.x = bndLabelWidth(context()->window->vg, -1, text.c_str()) + bndLabelWidth(context()->window->vg, -1, rightText.c_str()) + rightPadding;
Widget::step();
}

void MenuItem::onEnter(event::Enter &e) {
Menu *parentMenu = dynamic_cast<Menu*>(parent);
if (!parentMenu)
return;

parentMenu->activeEntry = NULL;

// Try to create child menu
Menu *childMenu = createChildMenu();
if (childMenu) {
parentMenu->activeEntry = this;
childMenu->box.pos = parent->box.pos.plus(box.getTopRight());
}
parentMenu->setChildMenu(childMenu);
}

void MenuItem::onDragDrop(event::DragDrop &e) {
if (e.origin != this)
return;
doAction();
}

void MenuItem::doAction() {
if (disabled)
return;

event::Action eAction;
// Consume event by default, but allow action to un-consume it to prevent the menu from being removed.
eAction.target = this;
onAction(eAction);
if (!eAction.target)
return;

Widget *overlay = getAncestorOfType<MenuOverlay>();
overlay->requestedDelete = true;
}


} // namespace rack

+ 206
- 0
src/ui/TextField.cpp View File

@@ -0,0 +1,206 @@
#include "ui/TextField.hpp"

namespace rack {


TextField::TextField() {
box.size.y = BND_WIDGET_HEIGHT;
}

void TextField::draw(NVGcontext *vg) {
nvgScissor(vg, 0, 0, box.size.x, box.size.y);

BNDwidgetState state;
if (this == context()->event->selectedWidget)
state = BND_ACTIVE;
else if (this == context()->event->hoveredWidget)
state = BND_HOVER;
else
state = BND_DEFAULT;

int begin = std::min(cursor, selection);
int end = std::max(cursor, selection);
bndTextField(vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_NONE, state, -1, text.c_str(), begin, end);
// Draw placeholder text
if (text.empty() && state != BND_ACTIVE) {
bndIconLabelCaret(vg, 0.0, 0.0, box.size.x, box.size.y, -1, bndGetTheme()->textFieldTheme.itemColor, 13, placeholder.c_str(), bndGetTheme()->textFieldTheme.itemColor, 0, -1);
}

nvgResetScissor(vg);
}

void TextField::onButton(event::Button &e) {
if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT) {
cursor = selection = getTextPosition(e.pos);
}
OpaqueWidget::onButton(e);
}

void TextField::onHover(event::Hover &e) {
if (this == context()->event->draggedWidget) {
int pos = getTextPosition(e.pos);
if (pos != selection) {
cursor = pos;
}
}
OpaqueWidget::onHover(e);
}

void TextField::onEnter(event::Enter &e) {
e.target = this;
}

void TextField::onSelectText(event::SelectText &e) {
if (e.codepoint < 128) {
std::string newText(1, (char) e.codepoint);
insertText(newText);
}
e.target = this;
}

void TextField::onSelectKey(event::SelectKey &e) {
if (e.action == GLFW_PRESS) {
switch (e.key) {
case GLFW_KEY_BACKSPACE: {
if (cursor == selection) {
cursor--;
if (cursor >= 0) {
text.erase(cursor, 1);
event::Change eChange;
onChange(eChange);
}
selection = cursor;
}
else {
int begin = std::min(cursor, selection);
text.erase(begin, std::abs(selection - cursor));
event::Change eChange;
onChange(eChange);
cursor = selection = begin;
}
} break;
case GLFW_KEY_DELETE: {
if (cursor == selection) {
text.erase(cursor, 1);
event::Change eChange;
onChange(eChange);
}
else {
int begin = std::min(cursor, selection);
text.erase(begin, std::abs(selection - cursor));
event::Change eChange;
onChange(eChange);
cursor = selection = begin;
}
} break;
case GLFW_KEY_LEFT: {
if (context()->window->isModPressed()) {
while (--cursor > 0) {
if (text[cursor] == ' ')
break;
}
}
else {
cursor--;
}
if (!context()->window->isShiftPressed()) {
selection = cursor;
}
} break;
case GLFW_KEY_RIGHT: {
if (context()->window->isModPressed()) {
while (++cursor < (int) text.size()) {
if (text[cursor] == ' ')
break;
}
}
else {
cursor++;
}
if (!context()->window->isShiftPressed()) {
selection = cursor;
}
} break;
case GLFW_KEY_HOME: {
selection = cursor = 0;
} break;
case GLFW_KEY_END: {
selection = cursor = text.size();
} break;
case GLFW_KEY_V: {
if (context()->window->isModPressed()) {
const char *newText = glfwGetClipboardString(context()->window->win);
if (newText)
insertText(newText);
}
} break;
case GLFW_KEY_X: {
if (context()->window->isModPressed()) {
if (cursor != selection) {
int begin = std::min(cursor, selection);
std::string selectedText = text.substr(begin, std::abs(selection - cursor));
glfwSetClipboardString(context()->window->win, selectedText.c_str());
insertText("");
}
}
} break;
case GLFW_KEY_C: {
if (context()->window->isModPressed()) {
if (cursor != selection) {
int begin = std::min(cursor, selection);
std::string selectedText = text.substr(begin, std::abs(selection - cursor));
glfwSetClipboardString(context()->window->win, selectedText.c_str());
}
}
} break;
case GLFW_KEY_A: {
if (context()->window->isModPressed()) {
selection = 0;
cursor = text.size();
}
} break;
case GLFW_KEY_ENTER: {
if (multiline) {
insertText("\n");
}
else {
event::Action eAction;
onAction(eAction);
}
} break;
}

cursor = math::clamp(cursor, 0, (int) text.size());
selection = math::clamp(selection, 0, (int) text.size());
e.target = this;
}
}

/** Inserts text at the cursor, replacing the selection if necessary */
void TextField::insertText(std::string text) {
if (cursor != selection) {
int begin = std::min(cursor, selection);
this->text.erase(begin, std::abs(selection - cursor));
cursor = selection = begin;
}
this->text.insert(cursor, text);
cursor += text.size();
selection = cursor;
event::Change eChange;
onChange(eChange);
}

/** Replaces the entire text */
void TextField::setText(std::string text) {
this->text = text;
selection = cursor = text.size();
event::Change eChange;
onChange(eChange);
}

int TextField::getTextPosition(math::Vec mousePos) {
return bndTextFieldTextPosition(context()->window->vg, 0.0, 0.0, box.size.x, box.size.y, -1, text.c_str(), mousePos.x, mousePos.y);
}


} // namespace rack

+ 0
- 1
src/window.cpp View File

@@ -1,5 +1,4 @@
#include "window.hpp"
#include "logger.hpp"
#include "asset.hpp"
#include "app/Scene.hpp"
#include "keyboard.hpp"


Loading…
Cancel
Save