@@ -26,7 +26,7 @@ struct ModuleWidget : widget::OpaqueWidget { | |||
math::Vec dragPos; | |||
math::Vec oldPos; | |||
ModuleWidget() {} | |||
ModuleWidget(); | |||
DEPRECATED ModuleWidget(engine::Module *module) { | |||
setModule(module); | |||
} | |||
@@ -1,5 +1,5 @@ | |||
#include "common.hpp" | |||
#include "sse_mathfun.h" | |||
#include <cstring> | |||
#include <emmintrin.h> | |||
@@ -7,7 +7,10 @@ namespace rack { | |||
namespace dsp { | |||
/** Casts an int to float, bitwise without conversion. */ | |||
inline float cast_i32_f32(int i) { | |||
static_assert(sizeof(int) == sizeof(float), "int and float must be the same size"); | |||
// Should be optimized to two `mov` instructions | |||
float f; | |||
std::memcpy(&f, &i, sizeof(f)); | |||
return f; | |||
@@ -21,6 +24,17 @@ inline int cast_f32_i32(float f) { | |||
} | |||
/** Generic class for vector float types. | |||
This class is designed to be used just like `float` scalars, with extra features for handling bitwise logic, conditions, loading, and storing. | |||
Usage example: | |||
float a[4], b[4]; | |||
f32_4 a = f32_4::load(in); | |||
f32_4 b = 2.f * a / (1 - a); | |||
b *= sin(2 * M_PI * a); | |||
b.store(out); | |||
*/ | |||
template <int N> | |||
struct f32; | |||
@@ -65,7 +79,7 @@ typedef f32<4> f32_4; | |||
// Operator overloads | |||
/** `a operator b` */ | |||
/** `a @ b` */ | |||
#define DECLARE_F32_4_OPERATOR_INFIX(operator, func) \ | |||
inline f32_4 operator(const f32_4 &a, const f32_4 &b) { \ | |||
return f32_4(func(a.v, b.v)); \ | |||
@@ -79,7 +93,7 @@ typedef f32<4> f32_4; | |||
return operator(a, f32_4(b)); \ | |||
} | |||
/** `a operator b` */ | |||
/** `a @= b` */ | |||
#define DECLARE_F32_4_OPERATOR_INCREMENT(operator, opfunc) \ | |||
inline f32_4 &operator(f32_4 &a, const f32_4 &b) { \ | |||
a = opfunc(a, b); \ | |||
@@ -95,7 +109,7 @@ DECLARE_F32_4_OPERATOR_INFIX(operator-, _mm_sub_ps) | |||
DECLARE_F32_4_OPERATOR_INFIX(operator*, _mm_mul_ps) | |||
DECLARE_F32_4_OPERATOR_INFIX(operator/, _mm_div_ps) | |||
/** Boolean operators on vectors give 0x00000000 for false and 0xffffffff for true, for each vector element. | |||
/** | |||
Use these to apply logic, bit masks, and conditions to elements. | |||
Examples: | |||
@@ -106,6 +120,8 @@ DECLARE_F32_4_OPERATOR_INFIX(operator^, _mm_xor_ps) | |||
DECLARE_F32_4_OPERATOR_INFIX(operator&, _mm_and_ps) | |||
DECLARE_F32_4_OPERATOR_INFIX(operator|, _mm_mul_ps) | |||
/** Boolean operators on vectors give 0x00000000 for false and 0xffffffff for true, for each vector element. | |||
*/ | |||
DECLARE_F32_4_OPERATOR_INCREMENT(operator+=, operator+); | |||
DECLARE_F32_4_OPERATOR_INCREMENT(operator-=, operator-); | |||
DECLARE_F32_4_OPERATOR_INCREMENT(operator*=, operator*); | |||
@@ -245,30 +245,42 @@ struct Zoom : Event { | |||
}; | |||
/** Occurs after the Widget's position is set by setPos(); | |||
/** Occurs after a Widget's position is set by Widget::setPos(). | |||
*/ | |||
struct Reposition : Event { | |||
}; | |||
/** Occurs after the Widget's size is set by setSize(); | |||
/** Occurs after a Widget's size is set by Widget::setSize(). | |||
*/ | |||
struct Resize : Event { | |||
}; | |||
/** Occurs after the Widget is added to a parent. | |||
/** Occurs after a Widget is added to a parent. | |||
*/ | |||
struct Add : Event { | |||
}; | |||
/** Occurs before the Widget is remove from its parent. | |||
/** Occurs before a Widget is removed from its parent. | |||
*/ | |||
struct Remove : Event { | |||
}; | |||
/** Occurs after a Widget is shown with Widget::show(). | |||
*/ | |||
struct Show : Event { | |||
}; | |||
/** Occurs after a Widget is hidden with Widget::hide(). | |||
*/ | |||
struct Hide : Event { | |||
}; | |||
struct State { | |||
widget::Widget *rootWidget = NULL; | |||
/** State widgets | |||
@@ -3,7 +3,7 @@ | |||
#include "plugin/Plugin.hpp" | |||
#include "plugin/Model.hpp" | |||
#include <vector> | |||
#include <list> | |||
#include <set> | |||
namespace rack { | |||
@@ -16,21 +16,21 @@ namespace plugin { | |||
void init(bool devMode); | |||
void destroy(); | |||
void logIn(std::string email, std::string password); | |||
void logIn(const std::string &email, const std::string &password); | |||
void logOut(); | |||
/** Returns whether a new plugin is available, and downloads it unless doing a dry run */ | |||
bool sync(bool dryRun); | |||
void cancelDownload(); | |||
bool isLoggedIn(); | |||
Plugin *getPlugin(std::string pluginSlug); | |||
Model *getModel(std::string pluginSlug, std::string modelSlug); | |||
std::string getAllowedTag(std::string tag); | |||
Plugin *getPlugin(const std::string &pluginSlug); | |||
Model *getModel(const std::string &pluginSlug, const std::string &modelSlug); | |||
std::string normalizeTag(const std::string &tag); | |||
/** Checks that the slug contains only alphanumeric characters, "-", and "_" */ | |||
bool isSlugValid(std::string slug); | |||
bool isSlugValid(const std::string &slug); | |||
extern const std::vector<std::string> allowedTags; | |||
extern std::list<Plugin*> plugins; | |||
extern std::vector<Plugin*> plugins; | |||
extern bool isDownloading; | |||
extern float downloadProgress; | |||
extern std::string downloadName; | |||
@@ -2,7 +2,7 @@ | |||
#include "common.hpp" | |||
#include "plugin/Plugin.hpp" | |||
#include <jansson.h> | |||
#include <list> | |||
#include <set> | |||
namespace rack { | |||
@@ -31,7 +31,7 @@ struct Model { | |||
/** Human readable name for your model, e.g. "Voltage Controlled Oscillator" */ | |||
std::string name; | |||
/** List of tags representing the function(s) of the module */ | |||
std::list<std::string> tags; | |||
std::set<std::string> tags; | |||
/** A one-line summary of the module's purpose */ | |||
std::string description; | |||
@@ -0,0 +1,19 @@ | |||
#pragma once | |||
#include "widget/Widget.hpp" | |||
#include "ui/common.hpp" | |||
namespace rack { | |||
namespace ui { | |||
/** Positions children with a margin between the layout's box. */ | |||
struct MarginLayout : widget::Widget { | |||
math::Vec margin; | |||
void step() override; | |||
}; | |||
} // namespace ui | |||
} // namespace rack |
@@ -40,6 +40,9 @@ struct Widget { | |||
void setPos(math::Vec pos); | |||
void setSize(math::Vec size); | |||
void show(); | |||
void hide(); | |||
void requestDelete(); | |||
virtual math::Rect getChildrenBoundingBox(); | |||
/** Returns `v` transformed into the coordinate system of `relative` */ | |||
@@ -156,6 +159,8 @@ struct Widget { | |||
virtual void onResize(const event::Resize &e) {} | |||
virtual void onAdd(const event::Add &e) {} | |||
virtual void onRemove(const event::Remove &e) {} | |||
virtual void onShow(const event::Show &e) {recurseEvent(&Widget::onShow, e);} | |||
virtual void onHide(const event::Hide &e) {recurseEvent(&Widget::onHide, e);} | |||
}; | |||
@@ -14,6 +14,7 @@ struct BlankPanel : Widget { | |||
void step() override { | |||
panelBorder->box.size = box.size; | |||
Widget::step(); | |||
} | |||
void draw(const DrawContext &ctx) override { | |||
@@ -16,7 +16,7 @@ void LedDisplay::draw(const widget::DrawContext &ctx) { | |||
nvgFillColor(ctx.vg, nvgRGB(0x00, 0x00, 0x00)); | |||
nvgFill(ctx.vg); | |||
nvgScissor(ctx.vg, 0, 0, box.size.x, box.size.y); | |||
nvgScissor(ctx.vg, RECT_ARGS(ctx.clipBox)); | |||
widget::Widget::draw(ctx); | |||
nvgResetScissor(ctx.vg); | |||
} | |||
@@ -79,7 +79,7 @@ LedDisplayTextField::LedDisplayTextField() { | |||
void LedDisplayTextField::draw(const widget::DrawContext &ctx) { | |||
nvgScissor(ctx.vg, 0, 0, box.size.x, box.size.y); | |||
nvgScissor(ctx.vg, RECT_ARGS(ctx.clipBox)); | |||
// Background | |||
nvgBeginPath(ctx.vg); | |||
@@ -4,9 +4,14 @@ | |||
#include "widget/ZoomWidget.hpp" | |||
#include "ui/ScrollWidget.hpp" | |||
#include "ui/SequentialLayout.hpp" | |||
#include "ui/MarginLayout.hpp" | |||
#include "ui/Label.hpp" | |||
#include "ui/TextField.hpp" | |||
#include "ui/MenuOverlay.hpp" | |||
#include "ui/List.hpp" | |||
#include "ui/MenuItem.hpp" | |||
#include "ui/Button.hpp" | |||
#include "ui/ChoiceButton.hpp" | |||
#include "app/ModuleWidget.hpp" | |||
#include "app/Scene.hpp" | |||
#include "plugin.hpp" | |||
@@ -68,96 +73,135 @@ struct BrowserOverlay : widget::OpaqueWidget { | |||
return; | |||
if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT) { | |||
this->visible = false; | |||
hide(); | |||
} | |||
} | |||
}; | |||
void onHoverKey(const event::HoverKey &e) override { | |||
if (e.action == GLFW_PRESS) { | |||
switch (e.key) { | |||
case GLFW_KEY_ESCAPE: { | |||
this->visible = false; | |||
e.consume(this); | |||
} break; | |||
} | |||
} | |||
if (!e.getConsumed()) | |||
widget::OpaqueWidget::onHoverKey(e); | |||
} | |||
}; | |||
static const float MODEL_BOX_ZOOM = 0.5f; | |||
struct ModelBox : widget::OpaqueWidget { | |||
plugin::Model *model; | |||
widget::Widget *infoWidget; | |||
/** Lazily created */ | |||
widget::Widget *previewWidget = NULL; | |||
/** Number of frames since draw() has been called */ | |||
int visibleFrames = 0; | |||
bool selected = false; | |||
ModelBox() { | |||
box.size.x = 0.f; | |||
box.size.y = std::ceil(RACK_GRID_HEIGHT * MODEL_BOX_ZOOM); | |||
} | |||
void setModel(plugin::Model *model) { | |||
this->model = model; | |||
box.size.x = 70.f; | |||
box.size.y = std::ceil(RACK_GRID_SIZE.y * 0.5f); | |||
infoWidget = new widget::Widget; | |||
infoWidget->box.size.x = 140; | |||
infoWidget->box.size.y = box.size.y; | |||
addChild(infoWidget); | |||
math::Vec p; | |||
p.y = box.size.y; | |||
box.size.y += 40.0; | |||
math::Vec pos; | |||
ui::Label *nameLabel = new ui::Label; | |||
// nameLabel->box.size.x = infoWidget->box.size.x; | |||
nameLabel->box.pos = pos; | |||
nameLabel->text = model->name; | |||
nameLabel->box.pos = p; | |||
p.y += nameLabel->box.size.y; | |||
addChild(nameLabel); | |||
infoWidget->addChild(nameLabel); | |||
pos = nameLabel->box.getBottomLeft(); | |||
ui::Label *pluginLabel = new ui::Label; | |||
// pluginLabel->box.size.x = infoWidget->box.size.x; | |||
pluginLabel->box.pos = pos; | |||
pluginLabel->text = model->plugin->name; | |||
pluginLabel->box.pos = p; | |||
p.y += pluginLabel->box.size.y; | |||
addChild(pluginLabel); | |||
infoWidget->addChild(pluginLabel); | |||
pos = pluginLabel->box.getBottomLeft(); | |||
ui::Label *descriptionLabel = new ui::Label; | |||
descriptionLabel->box.size.x = infoWidget->box.size.x; | |||
descriptionLabel->box.pos = pos; | |||
descriptionLabel->text = model->description; | |||
infoWidget->addChild(descriptionLabel); | |||
pos = descriptionLabel->box.getBottomLeft(); | |||
pos.y = infoWidget->box.size.y; | |||
for (const std::string &tag : model->tags) { | |||
ui::Button *tagButton = new ui::Button; | |||
tagButton->box.size.x = infoWidget->box.size.x; | |||
tagButton->box.pos = pos; | |||
tagButton->box.pos.y -= tagButton->box.size.y; | |||
tagButton->text = tag; | |||
infoWidget->addChild(tagButton); | |||
pos = tagButton->box.getTopLeft(); | |||
} | |||
ui::Button *favoriteButton = new ui::Button; | |||
favoriteButton->box.size.x = infoWidget->box.size.x; | |||
favoriteButton->box.pos = pos; | |||
favoriteButton->box.pos.y -= favoriteButton->box.size.y; | |||
favoriteButton->text = "★"; | |||
infoWidget->addChild(favoriteButton); | |||
pos = favoriteButton->box.getTopLeft(); | |||
} | |||
void createPreview() { | |||
assert(!previewWidget); | |||
previewWidget = new widget::TransparentWidget; | |||
previewWidget->box.size.y = std::ceil(RACK_GRID_HEIGHT * MODEL_BOX_ZOOM); | |||
addChild(previewWidget); | |||
widget::FramebufferWidget *fbWidget = new widget::FramebufferWidget; | |||
if (math::isNear(APP->window->pixelRatio, 1.0)) { | |||
// Small details draw poorly at low DPI, so oversample when drawing to the framebuffer | |||
fbWidget->oversample = 2.0; | |||
} | |||
previewWidget->addChild(fbWidget); | |||
widget::ZoomWidget *zoomWidget = new widget::ZoomWidget; | |||
zoomWidget->setZoom(MODEL_BOX_ZOOM); | |||
fbWidget->addChild(zoomWidget); | |||
ModuleWidget *moduleWidget = model->createModuleWidgetNull(); | |||
zoomWidget->addChild(moduleWidget); | |||
zoomWidget->box.size.x = moduleWidget->box.size.x * MODEL_BOX_ZOOM; | |||
zoomWidget->box.size.y = RACK_GRID_HEIGHT * MODEL_BOX_ZOOM; | |||
previewWidget->box.size.x = std::ceil(zoomWidget->box.size.x); | |||
// Reposition infoWidget | |||
infoWidget->box.pos.x = previewWidget->box.size.x; | |||
box.size.x = previewWidget->box.size.x + infoWidget->box.size.x; | |||
} | |||
void deletePreview() { | |||
assert(previewWidget); | |||
removeChild(previewWidget); | |||
delete previewWidget; | |||
previewWidget = NULL; | |||
} | |||
void step() override { | |||
if (previewWidget && ++visibleFrames >= 60) { | |||
removeChild(previewWidget); | |||
delete previewWidget; | |||
previewWidget = NULL; | |||
deletePreview(); | |||
} | |||
} | |||
void draw(const widget::DrawContext &ctx) override { | |||
visibleFrames = 0; | |||
// Lazily create ModuleWidget when drawn | |||
// Lazily create preview when drawn | |||
if (!previewWidget) { | |||
widget::Widget *transparentWidget = new widget::TransparentWidget; | |||
addChild(transparentWidget); | |||
widget::FramebufferWidget *fbWidget = new widget::FramebufferWidget; | |||
if (math::isNear(APP->window->pixelRatio, 1.0)) { | |||
// Small details draw poorly at low DPI, so oversample when drawing to the framebuffer | |||
fbWidget->oversample = 2.0; | |||
} | |||
transparentWidget->addChild(fbWidget); | |||
widget::ZoomWidget *zoomWidget = new widget::ZoomWidget; | |||
zoomWidget->setZoom(0.5f); | |||
fbWidget->addChild(zoomWidget); | |||
ModuleWidget *moduleWidget = model->createModuleWidgetNull(); | |||
zoomWidget->addChild(moduleWidget); | |||
zoomWidget->box.size.x = moduleWidget->box.size.x * zoomWidget->zoom; | |||
zoomWidget->box.size.y = RACK_GRID_HEIGHT; | |||
float width = std::ceil(zoomWidget->box.size.x); | |||
box.size.x = std::max(box.size.x, width); | |||
previewWidget = transparentWidget; | |||
createPreview(); | |||
} | |||
nvgScissor(ctx.vg, RECT_ARGS(ctx.clipBox)); | |||
widget::OpaqueWidget::draw(ctx); | |||
nvgResetScissor(ctx.vg); | |||
// Translucent overlay when selected | |||
if (selected) { | |||
nvgBeginPath(ctx.vg); | |||
nvgRect(ctx.vg, 0.0, 0.0, box.size.x, box.size.y); | |||
@@ -180,20 +224,82 @@ struct ModelBox : widget::OpaqueWidget { | |||
struct BrowserSearchField : ui::TextField { | |||
void step() override { | |||
// Steal focus when step is called | |||
APP->event->setSelected(this); | |||
ui::TextField::step(); | |||
} | |||
void onSelectKey(const event::SelectKey &e) override { | |||
if (e.action == GLFW_PRESS) { | |||
if (e.key == GLFW_KEY_ESCAPE) { | |||
BrowserOverlay *overlay = getAncestorOfType<BrowserOverlay>(); | |||
overlay->hide(); | |||
e.consume(this); | |||
} | |||
} | |||
if (!e.getConsumed()) | |||
ui::TextField::onSelectKey(e); | |||
} | |||
void onChange(const event::Change &e) override; | |||
void onHide(const event::Hide &e) override { | |||
setText(""); | |||
APP->event->setSelected(NULL); | |||
ui::TextField::onHide(e); | |||
} | |||
}; | |||
struct BrowserSidebar : widget::Widget { | |||
BrowserSearchField *searchField; | |||
ui::List *pluginList; | |||
ui::ScrollWidget *pluginScroll; | |||
ui::List *tagList; | |||
ui::ScrollWidget *tagScroll; | |||
BrowserSidebar() { | |||
searchField = new BrowserSearchField; | |||
addChild(searchField); | |||
pluginScroll = new ui::ScrollWidget; | |||
pluginScroll->box.pos = searchField->box.getBottomLeft(); | |||
addChild(pluginScroll); | |||
pluginList = new ui::List; | |||
pluginScroll->container->addChild(pluginList); | |||
std::set<std::string> pluginNames; | |||
for (plugin::Plugin *plugin : plugin::plugins) { | |||
pluginNames.insert(plugin->name); | |||
} | |||
for (const std::string &pluginName : pluginNames) { | |||
ui::MenuItem *item = new ui::MenuItem; | |||
item->text = pluginName; | |||
pluginList->addChild(item); | |||
} | |||
tagScroll = new ui::ScrollWidget; | |||
tagScroll->box.pos = searchField->box.getBottomLeft(); | |||
addChild(tagScroll); | |||
tagList = new ui::List; | |||
tagScroll->container->addChild(tagList); | |||
for (const std::string &tag : plugin::allowedTags) { | |||
ui::MenuItem *item = new ui::MenuItem; | |||
item->text = tag; | |||
tagList->addChild(item); | |||
} | |||
} | |||
void step() override { | |||
searchField->box.size.x = box.size.x; | |||
pluginScroll->box.size.y = box.size.y - searchField->box.size.y; | |||
pluginList->box.size.x = pluginScroll->box.size.x = box.size.x / 2; | |||
tagScroll->box.pos.x = box.size.x / 2; | |||
tagScroll->box.size.y = box.size.y - searchField->box.size.y; | |||
tagList->box.size.x = tagScroll->box.size.x = box.size.x / 2; | |||
widget::Widget::step(); | |||
} | |||
}; | |||
@@ -202,6 +308,7 @@ struct BrowserSidebar : widget::Widget { | |||
struct ModuleBrowser : widget::OpaqueWidget { | |||
BrowserSidebar *sidebar; | |||
ui::ScrollWidget *modelScroll; | |||
ui::MarginLayout *modelMargin; | |||
ui::SequentialLayout *modelContainer; | |||
ModuleBrowser() { | |||
@@ -212,9 +319,13 @@ struct ModuleBrowser : widget::OpaqueWidget { | |||
modelScroll = new ui::ScrollWidget; | |||
addChild(modelScroll); | |||
modelMargin = new ui::MarginLayout; | |||
modelMargin->margin = math::Vec(20, 20); | |||
modelScroll->container->addChild(modelMargin); | |||
modelContainer = new ui::SequentialLayout; | |||
modelContainer->spacing = math::Vec(10, 10); | |||
modelScroll->container->addChild(modelContainer); | |||
modelContainer->spacing = math::Vec(20, 20); | |||
modelMargin->addChild(modelContainer); | |||
for (plugin::Plugin *plugin : plugin::plugins) { | |||
for (plugin::Model *model : plugin->models) { | |||
@@ -233,8 +344,8 @@ struct ModuleBrowser : widget::OpaqueWidget { | |||
modelScroll->box.pos.x = sidebar->box.size.x; | |||
modelScroll->box.size.x = box.size.x - sidebar->box.size.x; | |||
modelScroll->box.size.y = box.size.y; | |||
modelContainer->box.size.x = modelScroll->box.size.x; | |||
modelContainer->box.size.y = modelContainer->getChildrenBoundingBox().getBottomRight().y; | |||
modelMargin->box.size.x = modelScroll->box.size.x; | |||
modelMargin->box.size.y = modelContainer->getChildrenBoundingBox().size.y + 2 * modelMargin->margin.y; | |||
widget::OpaqueWidget::step(); | |||
} | |||
@@ -251,6 +362,8 @@ struct ModuleBrowser : widget::OpaqueWidget { | |||
bool match = isModelMatch(modelBox->model, search); | |||
modelBox->visible = match; | |||
} | |||
// Reset scroll position | |||
modelScroll->offset = math::Vec(); | |||
} | |||
}; | |||
@@ -259,6 +372,10 @@ struct ModuleBrowser : widget::OpaqueWidget { | |||
void ModelBox::onButton(const event::Button &e) { | |||
widget::OpaqueWidget::onButton(e); | |||
if (e.getConsumed() != this) | |||
return; | |||
if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT) { | |||
// Create module | |||
ModuleWidget *moduleWidget = model->createModuleWidget(); | |||
@@ -268,9 +385,9 @@ void ModelBox::onButton(const event::Button &e) { | |||
// Pretend the moduleWidget was clicked so it can be dragged in the RackWidget | |||
e.consume(moduleWidget); | |||
// Close Module Browser | |||
// Hide Module Browser | |||
BrowserOverlay *overlay = getAncestorOfType<BrowserOverlay>(); | |||
overlay->visible = false; | |||
overlay->hide(); | |||
// Push ModuleAdd history action | |||
history::ModuleAdd *h = new history::ModuleAdd; | |||
@@ -278,7 +395,6 @@ void ModelBox::onButton(const event::Button &e) { | |||
h->setModule(moduleWidget); | |||
APP->history->push(h); | |||
} | |||
widget::OpaqueWidget::onButton(e); | |||
} | |||
void BrowserSearchField::onChange(const event::Change &e) { | |||
@@ -133,15 +133,21 @@ struct ModuleDeleteItem : ui::MenuItem { | |||
}; | |||
ModuleWidget::ModuleWidget() { | |||
box.size = math::Vec(0, RACK_GRID_HEIGHT); | |||
} | |||
ModuleWidget::~ModuleWidget() { | |||
setModule(NULL); | |||
} | |||
void ModuleWidget::draw(const widget::DrawContext &ctx) { | |||
nvgScissor(ctx.vg, RECT_ARGS(ctx.clipBox)); | |||
if (module && module->bypass) { | |||
nvgGlobalAlpha(ctx.vg, 0.25); | |||
} | |||
// nvgScissor(ctx.vg, 0, 0, box.size.x, box.size.y); | |||
widget::Widget::draw(ctx); | |||
// Power meter | |||
@@ -175,7 +181,7 @@ void ModuleWidget::draw(const widget::DrawContext &ctx) { | |||
// bndLabel(ctx.vg, 0, 0, INFINITY, INFINITY, -1, debugText.c_str()); | |||
// } | |||
// nvgResetScissor(ctx.vg); | |||
nvgResetScissor(ctx.vg); | |||
} | |||
void ModuleWidget::drawShadow(const widget::DrawContext &ctx) { | |||
@@ -307,13 +313,11 @@ void ModuleWidget::setPanel(std::shared_ptr<Svg> svg) { | |||
panel = NULL; | |||
} | |||
{ | |||
SvgPanel *panel = new SvgPanel; | |||
panel->setBackground(svg); | |||
addChild(panel); | |||
box.size = panel->box.size; | |||
this->panel = panel; | |||
} | |||
SvgPanel *svgPanel = new SvgPanel; | |||
svgPanel->setBackground(svg); | |||
addChild(svgPanel); | |||
box.size.x = svgPanel->box.size.x; | |||
panel = svgPanel; | |||
} | |||
void ModuleWidget::addParam(ParamWidget *param) { | |||
@@ -17,6 +17,14 @@ namespace rack { | |||
namespace engine { | |||
void disableDenormals() { | |||
// Set CPU to flush-to-zero (FTZ) and denormals-are-zero (DAZ) mode | |||
// https://software.intel.com/en-us/node/682949 | |||
_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON); | |||
_MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON); | |||
} | |||
/** Threads which obtain a VIPLock will cause wait() to block for other less important threads. | |||
This does not provide the VIPs with an exclusive lock. That should be left up to another mutex shared between the less important thread. | |||
*/ | |||
@@ -111,6 +119,7 @@ struct EngineWorker { | |||
void run() { | |||
system::setThreadName("Engine worker"); | |||
system::setThreadRealTime(); | |||
disableDenormals(); | |||
while (running) { | |||
step(); | |||
} | |||
@@ -179,7 +188,7 @@ Engine::~Engine() { | |||
static void Engine_stepModules(Engine *engine, int threadId) { | |||
Engine::Internal *internal = engine->internal; | |||
int threadCount = internal->threadCount; | |||
// int threadCount = internal->threadCount; | |||
int modulesLen = internal->modules.size(); | |||
// Step each module | |||
@@ -259,11 +268,7 @@ static void Engine_run(Engine *engine) { | |||
// Set up thread | |||
system::setThreadName("Engine"); | |||
system::setThreadRealTime(); | |||
// Set CPU to flush-to-zero (FTZ) and denormals-are-zero (DAZ) mode | |||
// https://software.intel.com/en-us/node/682949 | |||
_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON); | |||
_MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON); | |||
disableDenormals(); | |||
// Every time the engine waits and locks a mutex, it steps this many frames | |||
const int mutexSteps = 64; | |||
@@ -116,6 +116,17 @@ static bool loadPlugin(std::string path) { | |||
initCallback(plugin); | |||
plugin->fromJson(rootJ); | |||
// Normalize tags | |||
for (Model *model : plugin->models) { | |||
std::set<std::string> normalizedTags; | |||
for (const std::string &tag : model->tags) { | |||
std::string normalizedTag = normalizeTag(tag); | |||
if (!normalizedTag.empty()) | |||
normalizedTags.insert(normalizedTag); | |||
} | |||
model->tags = normalizedTags; | |||
} | |||
// Check slug | |||
if (!isSlugValid(plugin->slug)) { | |||
WARN("Plugin slug \"%s\" is invalid", plugin->slug.c_str()); | |||
@@ -305,7 +316,7 @@ static int extractZip(const char *filename, const char *path) { | |||
return err; | |||
} | |||
static void extractPackages(std::string path) { | |||
static void extractPackages(const std::string &path) { | |||
std::string message; | |||
for (std::string packagePath : system::listEntries(path)) { | |||
@@ -374,7 +385,7 @@ void destroy() { | |||
plugins.clear(); | |||
} | |||
void logIn(std::string email, std::string password) { | |||
void logIn(const std::string &email, const std::string &password) { | |||
json_t *reqJ = json_object(); | |||
json_object_set(reqJ, "email", json_string(email.c_str())); | |||
json_object_set(reqJ, "password", json_string(password.c_str())); | |||
@@ -496,7 +507,7 @@ bool isLoggedIn() { | |||
return settings.token != ""; | |||
} | |||
Plugin *getPlugin(std::string pluginSlug) { | |||
Plugin *getPlugin(const std::string &pluginSlug) { | |||
for (Plugin *plugin : plugins) { | |||
if (plugin->slug == pluginSlug) { | |||
return plugin; | |||
@@ -505,7 +516,7 @@ Plugin *getPlugin(std::string pluginSlug) { | |||
return NULL; | |||
} | |||
Model *getModel(std::string pluginSlug, std::string modelSlug) { | |||
Model *getModel(const std::string &pluginSlug, const std::string &modelSlug) { | |||
Plugin *plugin = getPlugin(pluginSlug); | |||
if (!plugin) | |||
return NULL; | |||
@@ -599,21 +610,21 @@ const std::map<std::string, std::string> tagAliases = { | |||
}; | |||
std::string getAllowedTag(std::string tag) { | |||
tag = string::lowercase(tag); | |||
std::string normalizeTag(const std::string &tag) { | |||
std::string lowercaseTag = string::lowercase(tag); | |||
// Transform aliases | |||
auto it = tagAliases.find(tag); | |||
auto it = tagAliases.find(lowercaseTag); | |||
if (it != tagAliases.end()) | |||
tag = it->second; | |||
lowercaseTag = it->second; | |||
// Find allowed tag | |||
for (std::string allowedTag : allowedTags) { | |||
if (tag == string::lowercase(allowedTag)) | |||
if (lowercaseTag == string::lowercase(allowedTag)) | |||
return allowedTag; | |||
} | |||
return ""; | |||
} | |||
bool isSlugValid(std::string slug) { | |||
bool isSlugValid(const std::string &slug) { | |||
for (char c : slug) { | |||
if (!(std::isalnum(c) || c == '-' || c == '_')) | |||
return false; | |||
@@ -622,7 +633,7 @@ bool isSlugValid(std::string slug) { | |||
} | |||
std::list<Plugin*> plugins; | |||
std::vector<Plugin*> plugins; | |||
bool isDownloading = false; | |||
float downloadProgress = 0.f; | |||
std::string downloadName; | |||
@@ -20,7 +20,7 @@ void Model::fromJson(json_t *rootJ) { | |||
json_t *tagJ; | |||
json_array_foreach(tagsJ, i, tagJ) { | |||
std::string tag = json_string_value(tagJ); | |||
tags.push_back(tag); | |||
tags.insert(tag); | |||
} | |||
} | |||
} | |||
@@ -0,0 +1,20 @@ | |||
#include "ui/MarginLayout.hpp" | |||
#include <vector> | |||
namespace rack { | |||
namespace ui { | |||
void MarginLayout::step() { | |||
widget::Widget::step(); | |||
math::Rect childBox = box.zeroPos().grow(margin.neg()); | |||
for (Widget *child : children) { | |||
child->box = childBox; | |||
} | |||
} | |||
} // namespace ui | |||
} // namespace rack |
@@ -78,7 +78,9 @@ void MenuItem::doAction() { | |||
return; | |||
widget::Widget *overlay = getAncestorOfType<MenuOverlay>(); | |||
overlay->requestedDelete = true; | |||
if (overlay) { | |||
overlay->requestedDelete = true; | |||
} | |||
} | |||
@@ -8,9 +8,6 @@ namespace rack { | |||
namespace ui { | |||
static const float SCROLLBAR_SENSITIVITY = 2.f; | |||
ScrollBar::ScrollBar() { | |||
box.size = math::Vec(BND_SCROLLBAR_WIDTH, BND_SCROLLBAR_HEIGHT); | |||
} | |||
@@ -26,12 +23,14 @@ void ScrollBar::onDragStart(const event::DragStart &e) { | |||
} | |||
void ScrollBar::onDragMove(const event::DragMove &e) { | |||
const float sensitivity = 1.f; | |||
ScrollWidget *scrollWidget = dynamic_cast<ScrollWidget*>(parent); | |||
assert(scrollWidget); | |||
if (orientation == HORIZONTAL) | |||
scrollWidget->offset.x += SCROLLBAR_SENSITIVITY * e.mouseDelta.x; | |||
scrollWidget->offset.x += sensitivity * e.mouseDelta.x; | |||
else | |||
scrollWidget->offset.y += SCROLLBAR_SENSITIVITY * e.mouseDelta.y; | |||
scrollWidget->offset.y += sensitivity * e.mouseDelta.y; | |||
} | |||
void ScrollBar::onDragEnd(const event::DragEnd &e) { | |||
@@ -28,7 +28,7 @@ void ScrollWidget::scrollTo(math::Rect r) { | |||
} | |||
void ScrollWidget::draw(const widget::DrawContext &ctx) { | |||
nvgScissor(ctx.vg, 0, 0, box.size.x, box.size.y); | |||
nvgScissor(ctx.vg, RECT_ARGS(ctx.clipBox)); | |||
widget::Widget::draw(ctx); | |||
nvgResetScissor(ctx.vg); | |||
} | |||
@@ -6,8 +6,9 @@ namespace rack { | |||
namespace ui { | |||
#define X(_v) (orientation == HORIZONTAL_ORIENTATION ? (_v).x : (_v).y) | |||
#define Y(_v) (orientation == HORIZONTAL_ORIENTATION ? (_v).y : (_v).x) | |||
#define X(v) (orientation == HORIZONTAL_ORIENTATION ? (v).x : (v).y) | |||
#define Y(v) (orientation == HORIZONTAL_ORIENTATION ? (v).y : (v).x) | |||
void SequentialLayout::step() { | |||
widget::Widget::step(); | |||
@@ -9,7 +9,7 @@ TextField::TextField() { | |||
} | |||
void TextField::draw(const widget::DrawContext &ctx) { | |||
nvgScissor(ctx.vg, 0, 0, box.size.x, box.size.y); | |||
nvgScissor(ctx.vg, RECT_ARGS(ctx.clipBox)); | |||
BNDwidgetState state; | |||
if (this == APP->event->selectedWidget) | |||
@@ -180,7 +180,6 @@ void TextField::onSelectKey(const event::SelectKey &e) { | |||
} | |||
} | |||
/** 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); | |||
@@ -194,12 +193,15 @@ void TextField::insertText(std::string text) { | |||
onChange(eChange); | |||
} | |||
/** Replaces the entire text */ | |||
void TextField::setText(std::string text) { | |||
bool changed = (text != this->text); | |||
this->text = text; | |||
selection = cursor = text.size(); | |||
event::Change eChange; | |||
onChange(eChange); | |||
if (changed) { | |||
// event::Change | |||
event::Change eChange; | |||
onChange(eChange); | |||
} | |||
} | |||
void TextField::selectAll() { | |||
@@ -28,6 +28,28 @@ void Widget::setSize(math::Vec size) { | |||
onResize(eResize); | |||
} | |||
void Widget::show() { | |||
if (visible) | |||
return; | |||
visible = true; | |||
// event::Show | |||
event::Show eShow; | |||
onShow(eShow); | |||
} | |||
void Widget::hide() { | |||
if (!visible) | |||
return; | |||
visible = false; | |||
// event::Hide | |||
event::Hide eHide; | |||
onHide(eHide); | |||
} | |||
void Widget::requestDelete() { | |||
requestedDelete = true; | |||
} | |||
math::Rect Widget::getChildrenBoundingBox() { | |||
math::Vec min = math::Vec(INFINITY, INFINITY); | |||
math::Vec max = math::Vec(-INFINITY, -INFINITY); | |||