@@ -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; | |||
@@ -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; | |||
std::string lastPath; | |||
math::Vec lastMousePos; | |||
bool lockModules = false; | |||
RackWidget(); | |||
~RackWidget(); | |||
@@ -1,4 +1,5 @@ | |||
#pragma once | |||
#include "logger.hpp" | |||
// Include most of the C stdlib for convenience | |||
#include <cstdlib> | |||
@@ -15,7 +15,6 @@ struct Engine { | |||
std::vector<Module*> modules; | |||
std::vector<Wire*> wires; | |||
bool paused = false; | |||
bool powerMeter = false; | |||
struct Internal; | |||
Internal *internal; | |||
@@ -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); | |||
}; | |||
@@ -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; | |||
} | |||
@@ -1,5 +1,4 @@ | |||
#pragma once | |||
#include "common.hpp" | |||
/** Example usage: | |||
@@ -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" | |||
@@ -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 |
@@ -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 { | |||
@@ -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; | |||
}; | |||
@@ -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; | |||
} | |||
}; | |||
@@ -13,6 +13,7 @@ struct SequentialLayout : virtual Widget { | |||
VERTICAL_ORIENTATION, | |||
}; | |||
Orientation orientation = HORIZONTAL_ORIENTATION; | |||
enum Alignment { | |||
LEFT_ALIGNMENT, | |||
CENTER_ALIGNMENT, | |||
@@ -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); | |||
}; | |||
@@ -6,7 +6,6 @@ namespace rack { | |||
void Knob::onDragStart(event::DragStart &e) { | |||
context()->window->cursorLock(); | |||
e.target = this; | |||
} | |||
void Knob::onDragEnd(event::DragEnd &e) { | |||
@@ -12,7 +12,6 @@ | |||
#include "ui/TextField.hpp" | |||
#include "plugin.hpp" | |||
#include "context.hpp" | |||
#include "logger.hpp" | |||
static const float itemMargin = 2.0; | |||
@@ -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); | |||
@@ -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); | |||
@@ -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 "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) { | |||
@@ -5,7 +5,6 @@ | |||
#include "app/ModuleBrowser.hpp" | |||
#include "app/RackScrollWidget.hpp" | |||
#include "context.hpp" | |||
#include "logger.hpp" | |||
#include <thread> | |||
@@ -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); | |||
@@ -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) { | |||
@@ -1,5 +1,4 @@ | |||
#include "audio.hpp" | |||
#include "logger.hpp" | |||
#include "string.hpp" | |||
#include "math.hpp" | |||
#include "bridge.hpp" | |||
@@ -1,6 +1,5 @@ | |||
#include "bridge.hpp" | |||
#include "string.hpp" | |||
#include "logger.hpp" | |||
#include "dsp/ringbuffer.hpp" | |||
#include <unistd.h> | |||
@@ -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(); | |||
@@ -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,4 +1,4 @@ | |||
#include "logger.hpp" | |||
#include "common.hpp" | |||
#include "asset.hpp" | |||
#include <chrono> | |||
@@ -1,6 +1,5 @@ | |||
#include "common.hpp" | |||
#include "random.hpp" | |||
#include "logger.hpp" | |||
#include "asset.hpp" | |||
#include "rtmidi.hpp" | |||
#include "keyboard.hpp" | |||
@@ -1,6 +1,5 @@ | |||
#include "plugin.hpp" | |||
#include "system.hpp" | |||
#include "logger.hpp" | |||
#include "network.hpp" | |||
#include "asset.hpp" | |||
#include "string.hpp" | |||
@@ -1,5 +1,4 @@ | |||
#include "plugin/Model.hpp" | |||
#include "logger.hpp" | |||
namespace rack { | |||
@@ -1,6 +1,5 @@ | |||
#include "plugin/Plugin.hpp" | |||
#include "plugin/Model.hpp" | |||
#include "logger.hpp" | |||
namespace rack { | |||
@@ -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 |
@@ -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 "logger.hpp" | |||
#include "asset.hpp" | |||
#include "app/Scene.hpp" | |||
#include "keyboard.hpp" | |||