@@ -26,7 +26,7 @@ struct ParamQuantity : Quantity { | |||||
float getMaxValue() override; | float getMaxValue() override; | ||||
float getDefaultValue() override; | float getDefaultValue() override; | ||||
float getDisplayValue() override; | float getDisplayValue() override; | ||||
void setDisplayValue(float displayValue) override ; | |||||
void setDisplayValue(float displayValue) override; | |||||
int getDisplayPrecision() override; | int getDisplayPrecision() override; | ||||
std::string getLabel() override; | std::string getLabel() override; | ||||
std::string getUnit() override; | std::string getUnit() override; | ||||
@@ -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 |
@@ -17,7 +17,6 @@ struct RackWidget : OpaqueWidget { | |||||
WireContainer *wireContainer; | WireContainer *wireContainer; | ||||
std::string lastPath; | std::string lastPath; | ||||
math::Vec lastMousePos; | math::Vec lastMousePos; | ||||
bool lockModules = false; | |||||
RackWidget(); | RackWidget(); | ||||
~RackWidget(); | ~RackWidget(); | ||||
@@ -1,4 +1,5 @@ | |||||
#pragma once | #pragma once | ||||
#include "logger.hpp" | |||||
// Include most of the C stdlib for convenience | // Include most of the C stdlib for convenience | ||||
#include <cstdlib> | #include <cstdlib> | ||||
@@ -15,7 +15,6 @@ struct Engine { | |||||
std::vector<Module*> modules; | std::vector<Module*> modules; | ||||
std::vector<Wire*> wires; | std::vector<Wire*> wires; | ||||
bool paused = false; | bool paused = false; | ||||
bool powerMeter = false; | |||||
struct Internal; | struct Internal; | ||||
Internal *internal; | Internal *internal; | ||||
@@ -153,7 +153,6 @@ struct DragMove : Event { | |||||
/** Occurs every frame when the mouse is hovering over a Widget while dragging. | /** 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 { | struct DragHover : Event, Position { | ||||
/** Change in mouse position since the last frame. Can be zero. */ | /** Change in mouse position since the last frame. Can be zero. */ | ||||
@@ -217,6 +216,13 @@ struct Context { | |||||
/** For middle-click dragging */ | /** For middle-click dragging */ | ||||
Widget *scrollWidget = NULL; | 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 handleButton(math::Vec pos, int button, int action, int mods); | ||||
void handleHover(math::Vec pos, math::Vec mouseDelta); | void handleHover(math::Vec pos, math::Vec mouseDelta); | ||||
void handleLeave(); | void handleLeave(); | ||||
@@ -225,8 +231,6 @@ struct Context { | |||||
void handleKey(math::Vec pos, int key, int scancode, int action, int mods); | void handleKey(math::Vec pos, int key, int scancode, int action, int mods); | ||||
void handleDrop(math::Vec pos, std::vector<std::string> paths); | void handleDrop(math::Vec pos, std::vector<std::string> paths); | ||||
void handleZoom(); | void handleZoom(); | ||||
/** Prepares a widget for deletion */ | |||||
void finalizeWidget(Widget *w); | |||||
}; | }; | ||||
@@ -5,6 +5,7 @@ | |||||
#include "app/Port.hpp" | #include "app/Port.hpp" | ||||
#include "app/ParamQuantity.hpp" | #include "app/ParamQuantity.hpp" | ||||
#include "app/ParamWidget.hpp" | #include "app/ParamWidget.hpp" | ||||
#include "app/Scene.hpp" | |||||
#include "engine/Module.hpp" | #include "engine/Module.hpp" | ||||
#include "context.hpp" | #include "context.hpp" | ||||
#include "window.hpp" | #include "window.hpp" | ||||
@@ -154,7 +155,7 @@ inline Menu *createMenu() { | |||||
MenuOverlay *menuOverlay = new MenuOverlay; | MenuOverlay *menuOverlay = new MenuOverlay; | ||||
menuOverlay->addChild(o); | menuOverlay->addChild(o); | ||||
context()->event->rootWidget->addChild(menuOverlay); | |||||
context()->scene->addChild(menuOverlay); | |||||
return o; | return o; | ||||
} | } | ||||
@@ -1,5 +1,4 @@ | |||||
#pragma once | #pragma once | ||||
#include "common.hpp" | |||||
/** Example usage: | /** Example usage: | ||||
@@ -3,7 +3,6 @@ | |||||
#include "common.hpp" | #include "common.hpp" | ||||
#include "math.hpp" | #include "math.hpp" | ||||
#include "string.hpp" | #include "string.hpp" | ||||
#include "logger.hpp" | |||||
#include "system.hpp" | #include "system.hpp" | ||||
#include "random.hpp" | #include "random.hpp" | ||||
#include "network.hpp" | #include "network.hpp" | ||||
@@ -52,7 +51,6 @@ | |||||
#include "app/MomentarySwitch.hpp" | #include "app/MomentarySwitch.hpp" | ||||
#include "app/MultiLightWidget.hpp" | #include "app/MultiLightWidget.hpp" | ||||
#include "app/ParamWidget.hpp" | #include "app/ParamWidget.hpp" | ||||
#include "app/PluginManagerWidget.hpp" | |||||
#include "app/Port.hpp" | #include "app/Port.hpp" | ||||
#include "app/RackRail.hpp" | #include "app/RackRail.hpp" | ||||
#include "app/Scene.hpp" | #include "app/Scene.hpp" | ||||
@@ -10,5 +10,13 @@ void save(std::string filename); | |||||
void load(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 settings | ||||
} // namespace rack | } // namespace rack |
@@ -24,7 +24,6 @@ struct Button : OpaqueWidget { | |||||
void draw(NVGcontext *vg) override { | 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()); | 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 { | void onEnter(event::Enter &e) override { | ||||
@@ -13,68 +13,12 @@ struct Menu : OpaqueWidget { | |||||
/** The entry which created the child menu */ | /** The entry which created the child menu */ | ||||
MenuEntry *activeEntry = NULL; | 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; | |||||
}; | }; | ||||
@@ -15,63 +15,14 @@ namespace rack { | |||||
struct MenuItem : MenuEntry { | struct MenuItem : MenuEntry { | ||||
std::string text; | std::string text; | ||||
std::string rightText; | 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;} | 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; | |||||
} | |||||
}; | }; | ||||
@@ -13,6 +13,7 @@ struct SequentialLayout : virtual Widget { | |||||
VERTICAL_ORIENTATION, | VERTICAL_ORIENTATION, | ||||
}; | }; | ||||
Orientation orientation = HORIZONTAL_ORIENTATION; | Orientation orientation = HORIZONTAL_ORIENTATION; | ||||
enum Alignment { | enum Alignment { | ||||
LEFT_ALIGNMENT, | LEFT_ALIGNMENT, | ||||
CENTER_ALIGNMENT, | CENTER_ALIGNMENT, | ||||
@@ -19,202 +19,20 @@ struct TextField : OpaqueWidget { | |||||
*/ | */ | ||||
int selection = 0; | 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 */ | /** 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 */ | /** 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); | |||||
}; | }; | ||||
@@ -6,7 +6,6 @@ namespace rack { | |||||
void Knob::onDragStart(event::DragStart &e) { | void Knob::onDragStart(event::DragStart &e) { | ||||
context()->window->cursorLock(); | context()->window->cursorLock(); | ||||
e.target = this; | |||||
} | } | ||||
void Knob::onDragEnd(event::DragEnd &e) { | void Knob::onDragEnd(event::DragEnd &e) { | ||||
@@ -12,7 +12,6 @@ | |||||
#include "ui/TextField.hpp" | #include "ui/TextField.hpp" | ||||
#include "plugin.hpp" | #include "plugin.hpp" | ||||
#include "context.hpp" | #include "context.hpp" | ||||
#include "logger.hpp" | |||||
static const float itemMargin = 2.0; | static const float itemMargin = 2.0; | ||||
@@ -1,11 +1,11 @@ | |||||
#include "app/ModuleWidget.hpp" | #include "app/ModuleWidget.hpp" | ||||
#include "engine/Engine.hpp" | #include "engine/Engine.hpp" | ||||
#include "logger.hpp" | |||||
#include "system.hpp" | #include "system.hpp" | ||||
#include "asset.hpp" | #include "asset.hpp" | ||||
#include "app/Scene.hpp" | #include "app/Scene.hpp" | ||||
#include "helpers.hpp" | #include "helpers.hpp" | ||||
#include "context.hpp" | #include "context.hpp" | ||||
#include "settings.hpp" | |||||
#include "osdialog.h" | #include "osdialog.h" | ||||
@@ -255,7 +255,7 @@ void ModuleWidget::draw(NVGcontext *vg) { | |||||
Widget::draw(vg); | Widget::draw(vg); | ||||
// Power meter | // Power meter | ||||
if (module && context()->engine->powerMeter) { | |||||
if (module && settings::powerMeter) { | |||||
nvgBeginPath(vg); | nvgBeginPath(vg); | ||||
nvgRect(vg, | nvgRect(vg, | ||||
0, box.size.y - 20, | 0, box.size.y - 20, | ||||
@@ -372,7 +372,7 @@ void ModuleWidget::onDragEnd(event::DragEnd &e) { | |||||
} | } | ||||
void ModuleWidget::onDragMove(event::DragMove &e) { | void ModuleWidget::onDragMove(event::DragMove &e) { | ||||
if (!context()->scene->rackWidget->lockModules) { | |||||
if (!settings::lockModules) { | |||||
math::Rect newBox = box; | math::Rect newBox = box; | ||||
newBox.pos = context()->scene->rackWidget->lastMousePos.minus(dragPos); | newBox.pos = context()->scene->rackWidget->lastMousePos.minus(dragPos); | ||||
context()->scene->rackWidget->requestModuleBoxNearest(this, newBox); | context()->scene->rackWidget->requestModuleBoxNearest(this, newBox); | ||||
@@ -41,6 +41,10 @@ float ParamQuantity::getDisplayValue() { | |||||
// Linear | // Linear | ||||
return getParam()->value * getParam()->displayMultiplier; | return getParam()->value * getParam()->displayMultiplier; | ||||
} | } | ||||
else if (getParam()->displayBase == 1.f) { | |||||
// Fixed (special case of exponential) | |||||
return getParam()->displayMultiplier; | |||||
} | |||||
else { | else { | ||||
// Exponential | // Exponential | ||||
return std::pow(getParam()->displayBase, getParam()->value) * getParam()->displayMultiplier; | return std::pow(getParam()->displayBase, getParam()->value) * getParam()->displayMultiplier; | ||||
@@ -52,6 +56,10 @@ void ParamQuantity::setDisplayValue(float displayValue) { | |||||
// Linear | // Linear | ||||
getParam()->value = displayValue / getParam()->displayMultiplier; | getParam()->value = displayValue / getParam()->displayMultiplier; | ||||
} | } | ||||
else if (getParam()->displayBase == 1.f) { | |||||
// Fixed | |||||
getParam()->value = getParam()->displayMultiplier; | |||||
} | |||||
else { | else { | ||||
// Exponential | // Exponential | ||||
getParam()->value = std::log(displayValue / getParam()->displayMultiplier) / std::log(getParam()->displayBase); | getParam()->value = std::log(displayValue / getParam()->displayMultiplier) / std::log(getParam()->displayBase); | ||||
@@ -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 |
@@ -8,7 +8,6 @@ | |||||
#include "settings.hpp" | #include "settings.hpp" | ||||
#include "asset.hpp" | #include "asset.hpp" | ||||
#include "system.hpp" | #include "system.hpp" | ||||
#include "logger.hpp" | |||||
#include "plugin.hpp" | #include "plugin.hpp" | ||||
#include "context.hpp" | #include "context.hpp" | ||||
@@ -511,7 +510,6 @@ void RackWidget::onHover(event::Hover &e) { | |||||
} | } | ||||
void RackWidget::onButton(event::Button &e) { | void RackWidget::onButton(event::Button &e) { | ||||
DEBUG("what"); | |||||
OpaqueWidget::onButton(e); | OpaqueWidget::onButton(e); | ||||
if (e.target == this) { | if (e.target == this) { | ||||
if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) { | if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) { | ||||
@@ -5,7 +5,6 @@ | |||||
#include "app/ModuleBrowser.hpp" | #include "app/ModuleBrowser.hpp" | ||||
#include "app/RackScrollWidget.hpp" | #include "app/RackScrollWidget.hpp" | ||||
#include "context.hpp" | #include "context.hpp" | ||||
#include "logger.hpp" | |||||
#include <thread> | #include <thread> | ||||
@@ -2,250 +2,531 @@ | |||||
#include "window.hpp" | #include "window.hpp" | ||||
#include "engine/Engine.hpp" | #include "engine/Engine.hpp" | ||||
#include "asset.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/SequentialLayout.hpp" | ||||
#include "ui/Slider.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 "app/Scene.hpp" | ||||
#include "context.hpp" | #include "context.hpp" | ||||
#include "settings.hpp" | |||||
#include "helpers.hpp" | #include "helpers.hpp" | ||||
#include "system.hpp" | |||||
#include "plugin.hpp" | |||||
#include <thread> | |||||
namespace rack { | 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 { | void onAction(event::Action &e) override { | ||||
context()->scene->rackWidget->reset(); | 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 { | void onAction(event::Action &e) override { | ||||
context()->scene->rackWidget->loadDialog(); | 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 { | void onAction(event::Action &e) override { | ||||
context()->scene->rackWidget->saveDialog(); | 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 { | void onAction(event::Action &e) override { | ||||
context()->scene->rackWidget->saveAsDialog(); | 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 { | void onAction(event::Action &e) override { | ||||
context()->scene->rackWidget->revert(); | 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 { | void onAction(event::Action &e) override { | ||||
context()->scene->rackWidget->disconnect(); | 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 { | 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 { | struct EnginePauseItem : MenuItem { | ||||
EnginePauseItem() { | |||||
text = "Pause engine"; | |||||
rightText = CHECKMARK(context()->engine->paused); | |||||
} | |||||
void onAction(event::Action &e) override { | void onAction(event::Action &e) override { | ||||
context()->engine->paused ^= true; | context()->engine->paused ^= true; | ||||
} | } | ||||
}; | }; | ||||
struct SampleRateItem : MenuItem { | |||||
struct SampleRateValueItem : MenuItem { | |||||
float sampleRate; | 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 { | void onAction(event::Action &e) override { | ||||
context()->engine->setSampleRate(sampleRate); | context()->engine->setSampleRate(sampleRate); | ||||
context()->engine->paused = false; | 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 { | void onAction(event::Action &e) override { | ||||
Menu *menu = createMenu(); | Menu *menu = createMenu(); | ||||
menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y)); | menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y)); | ||||
menu->box.size.x = box.size.x; | 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 { | 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 { | 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;} | 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() { | 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; | 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); | 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) { | 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); | bndBevel(vg, 0.0, 0.0, box.size.x, box.size.y); | ||||
Widget::draw(vg); | Widget::draw(vg); | ||||
@@ -5,6 +5,7 @@ | |||||
#include "window.hpp" | #include "window.hpp" | ||||
#include "event.hpp" | #include "event.hpp" | ||||
#include "context.hpp" | #include "context.hpp" | ||||
#include "settings.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -162,10 +163,8 @@ void WireWidget::fromJson(json_t *rootJ) { | |||||
} | } | ||||
void WireWidget::draw(NVGcontext *vg) { | 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; | WireWidget *activeWire = context()->scene->rackWidget->wireContainer->activeWire; | ||||
if (activeWire) { | if (activeWire) { | ||||
@@ -1,5 +1,4 @@ | |||||
#include "audio.hpp" | #include "audio.hpp" | ||||
#include "logger.hpp" | |||||
#include "string.hpp" | #include "string.hpp" | ||||
#include "math.hpp" | #include "math.hpp" | ||||
#include "bridge.hpp" | #include "bridge.hpp" | ||||
@@ -1,6 +1,5 @@ | |||||
#include "bridge.hpp" | #include "bridge.hpp" | ||||
#include "string.hpp" | #include "string.hpp" | ||||
#include "logger.hpp" | |||||
#include "dsp/ringbuffer.hpp" | #include "dsp/ringbuffer.hpp" | ||||
#include <unistd.h> | #include <unistd.h> | ||||
@@ -1,4 +1,5 @@ | |||||
#include "engine/Engine.hpp" | #include "engine/Engine.hpp" | ||||
#include "settings.hpp" | |||||
#include <algorithm> | #include <algorithm> | ||||
#include <chrono> | #include <chrono> | ||||
@@ -125,7 +126,7 @@ static void Engine_step(Engine *engine) { | |||||
// Step modules | // Step modules | ||||
for (Module *module : engine->modules) { | for (Module *module : engine->modules) { | ||||
if (engine->powerMeter) { | |||||
if (settings::powerMeter) { | |||||
auto startTime = std::chrono::high_resolution_clock::now(); | auto startTime = std::chrono::high_resolution_clock::now(); | ||||
module->step(); | module->step(); | ||||
@@ -1,12 +1,95 @@ | |||||
#include "event.hpp" | #include "event.hpp" | ||||
#include "widgets/Widget.hpp" | #include "widgets/Widget.hpp" | ||||
#include "logger.hpp" | |||||
namespace rack { | namespace rack { | ||||
namespace event { | 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) { | void Context::handleButton(math::Vec pos, int button, int action, int mods) { | ||||
// event::Button | // event::Button | ||||
event::Button eButton; | event::Button eButton; | ||||
@@ -18,48 +101,25 @@ void Context::handleButton(math::Vec pos, int button, int action, int mods) { | |||||
Widget *clickedWidget = eButton.target; | Widget *clickedWidget = eButton.target; | ||||
if (button == GLFW_MOUSE_BUTTON_LEFT) { | 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 | ||||
event::DragDrop eDragDrop; | event::DragDrop eDragDrop; | ||||
eDragDrop.origin = draggedWidget; | eDragDrop.origin = draggedWidget; | ||||
clickedWidget->onDragDrop(eDragDrop); | 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) { | void Context::handleHover(math::Vec pos, math::Vec mouseDelta) { | ||||
if (draggedWidget) { | if (draggedWidget) { | ||||
// event::DragMove | // event::DragMove | ||||
@@ -86,23 +145,8 @@ void Context::handleHover(math::Vec pos, math::Vec mouseDelta) { | |||||
eDragHover.pos = pos; | eDragHover.pos = pos; | ||||
eDragHover.mouseDelta = mouseDelta; | eDragHover.mouseDelta = mouseDelta; | ||||
rootWidget->onDragHover(eDragHover); | 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; | return; | ||||
} | } | ||||
@@ -120,32 +164,13 @@ void Context::handleHover(math::Vec pos, math::Vec mouseDelta) { | |||||
eHover.pos = pos; | eHover.pos = pos; | ||||
eHover.mouseDelta = mouseDelta; | eHover.mouseDelta = mouseDelta; | ||||
rootWidget->onHover(eHover); | 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() { | 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) { | 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); | 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() { | void Context::handleZoom() { | ||||
// event::Zoom | // event::Zoom | ||||
event::Zoom eZoom; | event::Zoom eZoom; | ||||
@@ -1,4 +1,4 @@ | |||||
#include "logger.hpp" | |||||
#include "common.hpp" | |||||
#include "asset.hpp" | #include "asset.hpp" | ||||
#include <chrono> | #include <chrono> | ||||
@@ -1,6 +1,5 @@ | |||||
#include "common.hpp" | #include "common.hpp" | ||||
#include "random.hpp" | #include "random.hpp" | ||||
#include "logger.hpp" | |||||
#include "asset.hpp" | #include "asset.hpp" | ||||
#include "rtmidi.hpp" | #include "rtmidi.hpp" | ||||
#include "keyboard.hpp" | #include "keyboard.hpp" | ||||
@@ -1,6 +1,5 @@ | |||||
#include "plugin.hpp" | #include "plugin.hpp" | ||||
#include "system.hpp" | #include "system.hpp" | ||||
#include "logger.hpp" | |||||
#include "network.hpp" | #include "network.hpp" | ||||
#include "asset.hpp" | #include "asset.hpp" | ||||
#include "string.hpp" | #include "string.hpp" | ||||
@@ -1,5 +1,4 @@ | |||||
#include "plugin/Model.hpp" | #include "plugin/Model.hpp" | ||||
#include "logger.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,6 +1,5 @@ | |||||
#include "plugin/Plugin.hpp" | #include "plugin/Plugin.hpp" | ||||
#include "plugin/Model.hpp" | #include "plugin/Model.hpp" | ||||
#include "logger.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,5 +1,4 @@ | |||||
#include "settings.hpp" | #include "settings.hpp" | ||||
#include "logger.hpp" | |||||
#include "window.hpp" | #include "window.hpp" | ||||
#include "plugin.hpp" | #include "plugin.hpp" | ||||
#include "app/Scene.hpp" | #include "app/Scene.hpp" | ||||
@@ -33,18 +32,15 @@ static json_t *settingsToJson() { | |||||
json_object_set_new(rootJ, "windowPos", windowPosJ); | 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 | // zoom | ||||
float zoom = context()->scene->zoomWidget->zoom; | |||||
json_t *zoomJ = json_real(zoom); | json_t *zoomJ = json_real(zoom); | ||||
json_object_set_new(rootJ, "zoom", zoomJ); | json_object_set_new(rootJ, "zoom", zoomJ); | ||||
@@ -69,10 +65,10 @@ static json_t *settingsToJson() { | |||||
json_object_set_new(rootJ, "moduleBrowser", moduleBrowserToJson()); | json_object_set_new(rootJ, "moduleBrowser", moduleBrowserToJson()); | ||||
// powerMeter | // powerMeter | ||||
json_object_set_new(rootJ, "powerMeter", json_boolean(context()->engine->powerMeter)); | |||||
json_object_set_new(rootJ, "powerMeter", json_boolean(powerMeter)); | |||||
// checkVersion | // checkVersion | ||||
json_object_set_new(rootJ, "checkVersion", json_boolean(context()->scene->checkVersion)); | |||||
json_object_set_new(rootJ, "checkVersion", json_boolean(checkVersion)); | |||||
return rootJ; | return rootJ; | ||||
} | } | ||||
@@ -99,21 +95,20 @@ static void settingsFromJson(json_t *rootJ) { | |||||
context()->window->setWindowPos(math::Vec(x, y)); | 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 | // tension | ||||
json_t *tensionJ = json_object_get(rootJ, "wireTension"); | json_t *tensionJ = json_object_get(rootJ, "wireTension"); | ||||
if (tensionJ) | if (tensionJ) | ||||
context()->scene->toolbar->wireTension = json_number_value(tensionJ); | |||||
wireTension = json_number_value(tensionJ); | |||||
// zoom | // zoom | ||||
json_t *zoomJ = json_object_get(rootJ, "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 | // allowCursorLock | ||||
json_t *allowCursorLockJ = json_object_get(rootJ, "allowCursorLock"); | json_t *allowCursorLockJ = json_object_get(rootJ, "allowCursorLock"); | ||||
@@ -145,12 +140,12 @@ static void settingsFromJson(json_t *rootJ) { | |||||
// powerMeter | // powerMeter | ||||
json_t *powerMeterJ = json_object_get(rootJ, "powerMeter"); | json_t *powerMeterJ = json_object_get(rootJ, "powerMeter"); | ||||
if (powerMeterJ) | if (powerMeterJ) | ||||
context()->engine->powerMeter = json_boolean_value(powerMeterJ); | |||||
powerMeter = json_boolean_value(powerMeterJ); | |||||
// checkVersion | // checkVersion | ||||
json_t *checkVersionJ = json_object_get(rootJ, "checkVersion"); | json_t *checkVersionJ = json_object_get(rootJ, "checkVersion"); | ||||
if (checkVersionJ) | 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 settings | ||||
} // namespace rack | } // namespace rack |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -1,5 +1,4 @@ | |||||
#include "window.hpp" | #include "window.hpp" | ||||
#include "logger.hpp" | |||||
#include "asset.hpp" | #include "asset.hpp" | ||||
#include "app/Scene.hpp" | #include "app/Scene.hpp" | ||||
#include "keyboard.hpp" | #include "keyboard.hpp" | ||||