Browse Source

ModuleBrowser WIP. Add Widget::show/hide().

tags/v1.0.0
Andrew Belt 6 years ago
parent
commit
13dadef857
21 changed files with 355 additions and 120 deletions
  1. +1
    -1
      include/app/ModuleWidget.hpp
  2. +20
    -4
      include/dsp/simd.hpp
  3. +16
    -4
      include/event.hpp
  4. +7
    -7
      include/plugin.hpp
  5. +2
    -2
      include/plugin/Model.hpp
  6. +19
    -0
      include/ui/MarginLayout.hpp
  7. +5
    -0
      include/widget/Widget.hpp
  8. +1
    -0
      src/Core/Blank.cpp
  9. +2
    -2
      src/app/LedDisplay.cpp
  10. +175
    -59
      src/app/ModuleBrowser.cpp
  11. +13
    -9
      src/app/ModuleWidget.cpp
  12. +11
    -6
      src/engine/Engine.cpp
  13. +22
    -11
      src/plugin.cpp
  14. +1
    -1
      src/plugin/Model.cpp
  15. +20
    -0
      src/ui/MarginLayout.cpp
  16. +3
    -1
      src/ui/MenuItem.cpp
  17. +4
    -5
      src/ui/ScrollBar.cpp
  18. +1
    -1
      src/ui/ScrollWidget.cpp
  19. +3
    -2
      src/ui/SequentialLayout.cpp
  20. +7
    -5
      src/ui/TextField.cpp
  21. +22
    -0
      src/widget/Widget.cpp

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

@@ -26,7 +26,7 @@ struct ModuleWidget : widget::OpaqueWidget {
math::Vec dragPos; math::Vec dragPos;
math::Vec oldPos; math::Vec oldPos;


ModuleWidget() {}
ModuleWidget();
DEPRECATED ModuleWidget(engine::Module *module) { DEPRECATED ModuleWidget(engine::Module *module) {
setModule(module); setModule(module);
} }


+ 20
- 4
include/dsp/simd.hpp View File

@@ -1,5 +1,5 @@
#include "common.hpp"
#include "sse_mathfun.h" #include "sse_mathfun.h"
#include <cstring>
#include <emmintrin.h> #include <emmintrin.h>




@@ -7,7 +7,10 @@ namespace rack {
namespace dsp { namespace dsp {




/** Casts an int to float, bitwise without conversion. */
inline float cast_i32_f32(int i) { 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; float f;
std::memcpy(&f, &i, sizeof(f)); std::memcpy(&f, &i, sizeof(f));
return 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> template <int N>
struct f32; struct f32;


@@ -65,7 +79,7 @@ typedef f32<4> f32_4;
// Operator overloads // Operator overloads




/** `a operator b` */
/** `a @ b` */
#define DECLARE_F32_4_OPERATOR_INFIX(operator, func) \ #define DECLARE_F32_4_OPERATOR_INFIX(operator, func) \
inline f32_4 operator(const f32_4 &a, const f32_4 &b) { \ inline f32_4 operator(const f32_4 &a, const f32_4 &b) { \
return f32_4(func(a.v, b.v)); \ return f32_4(func(a.v, b.v)); \
@@ -79,7 +93,7 @@ typedef f32<4> f32_4;
return operator(a, f32_4(b)); \ return operator(a, f32_4(b)); \
} }


/** `a operator b` */
/** `a @= b` */
#define DECLARE_F32_4_OPERATOR_INCREMENT(operator, opfunc) \ #define DECLARE_F32_4_OPERATOR_INCREMENT(operator, opfunc) \
inline f32_4 &operator(f32_4 &a, const f32_4 &b) { \ inline f32_4 &operator(f32_4 &a, const f32_4 &b) { \
a = opfunc(a, 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_mul_ps)
DECLARE_F32_4_OPERATOR_INFIX(operator/, _mm_div_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. Use these to apply logic, bit masks, and conditions to elements.
Examples: 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_and_ps)
DECLARE_F32_4_OPERATOR_INFIX(operator|, _mm_mul_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-); DECLARE_F32_4_OPERATOR_INCREMENT(operator-=, operator-);
DECLARE_F32_4_OPERATOR_INCREMENT(operator*=, operator*); DECLARE_F32_4_OPERATOR_INCREMENT(operator*=, operator*);


+ 16
- 4
include/event.hpp View File

@@ -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 { 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 { struct Resize : Event {
}; };




/** Occurs after the Widget is added to a parent.
/** Occurs after a Widget is added to a parent.
*/ */
struct Add : Event { struct Add : Event {
}; };




/** Occurs before the Widget is remove from its parent.
/** Occurs before a Widget is removed from its parent.
*/ */
struct Remove : Event { 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 { struct State {
widget::Widget *rootWidget = NULL; widget::Widget *rootWidget = NULL;
/** State widgets /** State widgets


+ 7
- 7
include/plugin.hpp View File

@@ -3,7 +3,7 @@
#include "plugin/Plugin.hpp" #include "plugin/Plugin.hpp"
#include "plugin/Model.hpp" #include "plugin/Model.hpp"
#include <vector> #include <vector>
#include <list>
#include <set>




namespace rack { namespace rack {
@@ -16,21 +16,21 @@ namespace plugin {


void init(bool devMode); void init(bool devMode);
void destroy(); void destroy();
void logIn(std::string email, std::string password);
void logIn(const std::string &email, const std::string &password);
void logOut(); void logOut();
/** Returns whether a new plugin is available, and downloads it unless doing a dry run */ /** Returns whether a new plugin is available, and downloads it unless doing a dry run */
bool sync(bool dryRun); bool sync(bool dryRun);
void cancelDownload(); void cancelDownload();
bool isLoggedIn(); 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 "_" */ /** 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 const std::vector<std::string> allowedTags;
extern std::list<Plugin*> plugins;
extern std::vector<Plugin*> plugins;
extern bool isDownloading; extern bool isDownloading;
extern float downloadProgress; extern float downloadProgress;
extern std::string downloadName; extern std::string downloadName;


+ 2
- 2
include/plugin/Model.hpp View File

@@ -2,7 +2,7 @@
#include "common.hpp" #include "common.hpp"
#include "plugin/Plugin.hpp" #include "plugin/Plugin.hpp"
#include <jansson.h> #include <jansson.h>
#include <list>
#include <set>




namespace rack { namespace rack {
@@ -31,7 +31,7 @@ struct Model {
/** Human readable name for your model, e.g. "Voltage Controlled Oscillator" */ /** Human readable name for your model, e.g. "Voltage Controlled Oscillator" */
std::string name; std::string name;
/** List of tags representing the function(s) of the module */ /** 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 */ /** A one-line summary of the module's purpose */
std::string description; std::string description;




+ 19
- 0
include/ui/MarginLayout.hpp View File

@@ -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

+ 5
- 0
include/widget/Widget.hpp View File

@@ -40,6 +40,9 @@ struct Widget {


void setPos(math::Vec pos); void setPos(math::Vec pos);
void setSize(math::Vec size); void setSize(math::Vec size);
void show();
void hide();
void requestDelete();


virtual math::Rect getChildrenBoundingBox(); virtual math::Rect getChildrenBoundingBox();
/** Returns `v` transformed into the coordinate system of `relative` */ /** Returns `v` transformed into the coordinate system of `relative` */
@@ -156,6 +159,8 @@ struct Widget {
virtual void onResize(const event::Resize &e) {} virtual void onResize(const event::Resize &e) {}
virtual void onAdd(const event::Add &e) {} virtual void onAdd(const event::Add &e) {}
virtual void onRemove(const event::Remove &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);}
}; };






+ 1
- 0
src/Core/Blank.cpp View File

@@ -14,6 +14,7 @@ struct BlankPanel : Widget {


void step() override { void step() override {
panelBorder->box.size = box.size; panelBorder->box.size = box.size;
Widget::step();
} }


void draw(const DrawContext &ctx) override { void draw(const DrawContext &ctx) override {


+ 2
- 2
src/app/LedDisplay.cpp View File

@@ -16,7 +16,7 @@ void LedDisplay::draw(const widget::DrawContext &ctx) {
nvgFillColor(ctx.vg, nvgRGB(0x00, 0x00, 0x00)); nvgFillColor(ctx.vg, nvgRGB(0x00, 0x00, 0x00));
nvgFill(ctx.vg); 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); widget::Widget::draw(ctx);
nvgResetScissor(ctx.vg); nvgResetScissor(ctx.vg);
} }
@@ -79,7 +79,7 @@ LedDisplayTextField::LedDisplayTextField() {




void LedDisplayTextField::draw(const widget::DrawContext &ctx) { 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 // Background
nvgBeginPath(ctx.vg); nvgBeginPath(ctx.vg);


+ 175
- 59
src/app/ModuleBrowser.cpp View File

@@ -4,9 +4,14 @@
#include "widget/ZoomWidget.hpp" #include "widget/ZoomWidget.hpp"
#include "ui/ScrollWidget.hpp" #include "ui/ScrollWidget.hpp"
#include "ui/SequentialLayout.hpp" #include "ui/SequentialLayout.hpp"
#include "ui/MarginLayout.hpp"
#include "ui/Label.hpp" #include "ui/Label.hpp"
#include "ui/TextField.hpp" #include "ui/TextField.hpp"
#include "ui/MenuOverlay.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/ModuleWidget.hpp"
#include "app/Scene.hpp" #include "app/Scene.hpp"
#include "plugin.hpp" #include "plugin.hpp"
@@ -68,96 +73,135 @@ struct BrowserOverlay : widget::OpaqueWidget {
return; return;


if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT) { 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 { struct ModelBox : widget::OpaqueWidget {
plugin::Model *model; plugin::Model *model;
widget::Widget *infoWidget;
/** Lazily created */ /** Lazily created */
widget::Widget *previewWidget = NULL; widget::Widget *previewWidget = NULL;
/** Number of frames since draw() has been called */ /** Number of frames since draw() has been called */
int visibleFrames = 0; int visibleFrames = 0;
bool selected = false; 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) { void setModel(plugin::Model *model) {
this->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; ui::Label *nameLabel = new ui::Label;
// nameLabel->box.size.x = infoWidget->box.size.x;
nameLabel->box.pos = pos;
nameLabel->text = model->name; 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; ui::Label *pluginLabel = new ui::Label;
// pluginLabel->box.size.x = infoWidget->box.size.x;
pluginLabel->box.pos = pos;
pluginLabel->text = model->plugin->name; 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 { void step() override {
if (previewWidget && ++visibleFrames >= 60) { if (previewWidget && ++visibleFrames >= 60) {
removeChild(previewWidget);
delete previewWidget;
previewWidget = NULL;
deletePreview();
} }
} }


void draw(const widget::DrawContext &ctx) override { void draw(const widget::DrawContext &ctx) override {
visibleFrames = 0; visibleFrames = 0;


// Lazily create ModuleWidget when drawn
// Lazily create preview when drawn
if (!previewWidget) { 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); widget::OpaqueWidget::draw(ctx);
nvgResetScissor(ctx.vg);

// Translucent overlay when selected
if (selected) { if (selected) {
nvgBeginPath(ctx.vg); nvgBeginPath(ctx.vg);
nvgRect(ctx.vg, 0.0, 0.0, box.size.x, box.size.y); 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 { 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 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 { struct BrowserSidebar : widget::Widget {
BrowserSearchField *searchField; BrowserSearchField *searchField;
ui::List *pluginList;
ui::ScrollWidget *pluginScroll;
ui::List *tagList;
ui::ScrollWidget *tagScroll;


BrowserSidebar() { BrowserSidebar() {
searchField = new BrowserSearchField; searchField = new BrowserSearchField;
addChild(searchField); 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 { void step() override {
searchField->box.size.x = box.size.x; 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(); widget::Widget::step();
} }
}; };
@@ -202,6 +308,7 @@ struct BrowserSidebar : widget::Widget {
struct ModuleBrowser : widget::OpaqueWidget { struct ModuleBrowser : widget::OpaqueWidget {
BrowserSidebar *sidebar; BrowserSidebar *sidebar;
ui::ScrollWidget *modelScroll; ui::ScrollWidget *modelScroll;
ui::MarginLayout *modelMargin;
ui::SequentialLayout *modelContainer; ui::SequentialLayout *modelContainer;


ModuleBrowser() { ModuleBrowser() {
@@ -212,9 +319,13 @@ struct ModuleBrowser : widget::OpaqueWidget {
modelScroll = new ui::ScrollWidget; modelScroll = new ui::ScrollWidget;
addChild(modelScroll); addChild(modelScroll);


modelMargin = new ui::MarginLayout;
modelMargin->margin = math::Vec(20, 20);
modelScroll->container->addChild(modelMargin);

modelContainer = new ui::SequentialLayout; 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::Plugin *plugin : plugin::plugins) {
for (plugin::Model *model : plugin->models) { for (plugin::Model *model : plugin->models) {
@@ -233,8 +344,8 @@ struct ModuleBrowser : widget::OpaqueWidget {
modelScroll->box.pos.x = sidebar->box.size.x; modelScroll->box.pos.x = sidebar->box.size.x;
modelScroll->box.size.x = box.size.x - sidebar->box.size.x; modelScroll->box.size.x = box.size.x - sidebar->box.size.x;
modelScroll->box.size.y = box.size.y; 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(); widget::OpaqueWidget::step();
} }
@@ -251,6 +362,8 @@ struct ModuleBrowser : widget::OpaqueWidget {
bool match = isModelMatch(modelBox->model, search); bool match = isModelMatch(modelBox->model, search);
modelBox->visible = match; 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) { 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) { if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT) {
// Create module // Create module
ModuleWidget *moduleWidget = model->createModuleWidget(); 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 // Pretend the moduleWidget was clicked so it can be dragged in the RackWidget
e.consume(moduleWidget); e.consume(moduleWidget);


// Close Module Browser
// Hide Module Browser
BrowserOverlay *overlay = getAncestorOfType<BrowserOverlay>(); BrowserOverlay *overlay = getAncestorOfType<BrowserOverlay>();
overlay->visible = false;
overlay->hide();


// Push ModuleAdd history action // Push ModuleAdd history action
history::ModuleAdd *h = new history::ModuleAdd; history::ModuleAdd *h = new history::ModuleAdd;
@@ -278,7 +395,6 @@ void ModelBox::onButton(const event::Button &e) {
h->setModule(moduleWidget); h->setModule(moduleWidget);
APP->history->push(h); APP->history->push(h);
} }
widget::OpaqueWidget::onButton(e);
} }


void BrowserSearchField::onChange(const event::Change &e) { void BrowserSearchField::onChange(const event::Change &e) {


+ 13
- 9
src/app/ModuleWidget.cpp View File

@@ -133,15 +133,21 @@ struct ModuleDeleteItem : ui::MenuItem {
}; };




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

ModuleWidget::~ModuleWidget() { ModuleWidget::~ModuleWidget() {
setModule(NULL); setModule(NULL);
} }


void ModuleWidget::draw(const widget::DrawContext &ctx) { void ModuleWidget::draw(const widget::DrawContext &ctx) {
nvgScissor(ctx.vg, RECT_ARGS(ctx.clipBox));

if (module && module->bypass) { if (module && module->bypass) {
nvgGlobalAlpha(ctx.vg, 0.25); nvgGlobalAlpha(ctx.vg, 0.25);
} }
// nvgScissor(ctx.vg, 0, 0, box.size.x, box.size.y);
widget::Widget::draw(ctx); widget::Widget::draw(ctx);


// Power meter // Power meter
@@ -175,7 +181,7 @@ void ModuleWidget::draw(const widget::DrawContext &ctx) {
// bndLabel(ctx.vg, 0, 0, INFINITY, INFINITY, -1, debugText.c_str()); // bndLabel(ctx.vg, 0, 0, INFINITY, INFINITY, -1, debugText.c_str());
// } // }


// nvgResetScissor(ctx.vg);
nvgResetScissor(ctx.vg);
} }


void ModuleWidget::drawShadow(const widget::DrawContext &ctx) { void ModuleWidget::drawShadow(const widget::DrawContext &ctx) {
@@ -307,13 +313,11 @@ void ModuleWidget::setPanel(std::shared_ptr<Svg> svg) {
panel = NULL; 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) { void ModuleWidget::addParam(ParamWidget *param) {


+ 11
- 6
src/engine/Engine.cpp View File

@@ -17,6 +17,14 @@ namespace rack {
namespace engine { 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. /** 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. 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() { void run() {
system::setThreadName("Engine worker"); system::setThreadName("Engine worker");
system::setThreadRealTime(); system::setThreadRealTime();
disableDenormals();
while (running) { while (running) {
step(); step();
} }
@@ -179,7 +188,7 @@ Engine::~Engine() {
static void Engine_stepModules(Engine *engine, int threadId) { static void Engine_stepModules(Engine *engine, int threadId) {
Engine::Internal *internal = engine->internal; Engine::Internal *internal = engine->internal;


int threadCount = internal->threadCount;
// int threadCount = internal->threadCount;
int modulesLen = internal->modules.size(); int modulesLen = internal->modules.size();


// Step each module // Step each module
@@ -259,11 +268,7 @@ static void Engine_run(Engine *engine) {
// Set up thread // Set up thread
system::setThreadName("Engine"); system::setThreadName("Engine");
system::setThreadRealTime(); 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 // Every time the engine waits and locks a mutex, it steps this many frames
const int mutexSteps = 64; const int mutexSteps = 64;


+ 22
- 11
src/plugin.cpp View File

@@ -116,6 +116,17 @@ static bool loadPlugin(std::string path) {
initCallback(plugin); initCallback(plugin);
plugin->fromJson(rootJ); 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 // Check slug
if (!isSlugValid(plugin->slug)) { if (!isSlugValid(plugin->slug)) {
WARN("Plugin slug \"%s\" is invalid", plugin->slug.c_str()); 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; return err;
} }


static void extractPackages(std::string path) {
static void extractPackages(const std::string &path) {
std::string message; std::string message;


for (std::string packagePath : system::listEntries(path)) { for (std::string packagePath : system::listEntries(path)) {
@@ -374,7 +385,7 @@ void destroy() {
plugins.clear(); 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_t *reqJ = json_object();
json_object_set(reqJ, "email", json_string(email.c_str())); json_object_set(reqJ, "email", json_string(email.c_str()));
json_object_set(reqJ, "password", json_string(password.c_str())); json_object_set(reqJ, "password", json_string(password.c_str()));
@@ -496,7 +507,7 @@ bool isLoggedIn() {
return settings.token != ""; return settings.token != "";
} }


Plugin *getPlugin(std::string pluginSlug) {
Plugin *getPlugin(const std::string &pluginSlug) {
for (Plugin *plugin : plugins) { for (Plugin *plugin : plugins) {
if (plugin->slug == pluginSlug) { if (plugin->slug == pluginSlug) {
return plugin; return plugin;
@@ -505,7 +516,7 @@ Plugin *getPlugin(std::string pluginSlug) {
return NULL; return NULL;
} }


Model *getModel(std::string pluginSlug, std::string modelSlug) {
Model *getModel(const std::string &pluginSlug, const std::string &modelSlug) {
Plugin *plugin = getPlugin(pluginSlug); Plugin *plugin = getPlugin(pluginSlug);
if (!plugin) if (!plugin)
return NULL; 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 // Transform aliases
auto it = tagAliases.find(tag);
auto it = tagAliases.find(lowercaseTag);
if (it != tagAliases.end()) if (it != tagAliases.end())
tag = it->second;
lowercaseTag = it->second;
// Find allowed tag // Find allowed tag
for (std::string allowedTag : allowedTags) { for (std::string allowedTag : allowedTags) {
if (tag == string::lowercase(allowedTag))
if (lowercaseTag == string::lowercase(allowedTag))
return allowedTag; return allowedTag;
} }
return ""; return "";
} }


bool isSlugValid(std::string slug) {
bool isSlugValid(const std::string &slug) {
for (char c : slug) { for (char c : slug) {
if (!(std::isalnum(c) || c == '-' || c == '_')) if (!(std::isalnum(c) || c == '-' || c == '_'))
return false; return false;
@@ -622,7 +633,7 @@ bool isSlugValid(std::string slug) {
} }




std::list<Plugin*> plugins;
std::vector<Plugin*> plugins;
bool isDownloading = false; bool isDownloading = false;
float downloadProgress = 0.f; float downloadProgress = 0.f;
std::string downloadName; std::string downloadName;


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

@@ -20,7 +20,7 @@ void Model::fromJson(json_t *rootJ) {
json_t *tagJ; json_t *tagJ;
json_array_foreach(tagsJ, i, tagJ) { json_array_foreach(tagsJ, i, tagJ) {
std::string tag = json_string_value(tagJ); std::string tag = json_string_value(tagJ);
tags.push_back(tag);
tags.insert(tag);
} }
} }
} }


+ 20
- 0
src/ui/MarginLayout.cpp View File

@@ -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

+ 3
- 1
src/ui/MenuItem.cpp View File

@@ -78,7 +78,9 @@ void MenuItem::doAction() {
return; return;


widget::Widget *overlay = getAncestorOfType<MenuOverlay>(); widget::Widget *overlay = getAncestorOfType<MenuOverlay>();
overlay->requestedDelete = true;
if (overlay) {
overlay->requestedDelete = true;
}
} }






+ 4
- 5
src/ui/ScrollBar.cpp View File

@@ -8,9 +8,6 @@ namespace rack {
namespace ui { namespace ui {




static const float SCROLLBAR_SENSITIVITY = 2.f;


ScrollBar::ScrollBar() { ScrollBar::ScrollBar() {
box.size = math::Vec(BND_SCROLLBAR_WIDTH, BND_SCROLLBAR_HEIGHT); 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) { void ScrollBar::onDragMove(const event::DragMove &e) {
const float sensitivity = 1.f;

ScrollWidget *scrollWidget = dynamic_cast<ScrollWidget*>(parent); ScrollWidget *scrollWidget = dynamic_cast<ScrollWidget*>(parent);
assert(scrollWidget); assert(scrollWidget);
if (orientation == HORIZONTAL) if (orientation == HORIZONTAL)
scrollWidget->offset.x += SCROLLBAR_SENSITIVITY * e.mouseDelta.x;
scrollWidget->offset.x += sensitivity * e.mouseDelta.x;
else else
scrollWidget->offset.y += SCROLLBAR_SENSITIVITY * e.mouseDelta.y;
scrollWidget->offset.y += sensitivity * e.mouseDelta.y;
} }


void ScrollBar::onDragEnd(const event::DragEnd &e) { void ScrollBar::onDragEnd(const event::DragEnd &e) {


+ 1
- 1
src/ui/ScrollWidget.cpp View File

@@ -28,7 +28,7 @@ void ScrollWidget::scrollTo(math::Rect r) {
} }


void ScrollWidget::draw(const widget::DrawContext &ctx) { 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); widget::Widget::draw(ctx);
nvgResetScissor(ctx.vg); nvgResetScissor(ctx.vg);
} }


+ 3
- 2
src/ui/SequentialLayout.cpp View File

@@ -6,8 +6,9 @@ namespace rack {
namespace ui { 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() { void SequentialLayout::step() {
widget::Widget::step(); widget::Widget::step();


+ 7
- 5
src/ui/TextField.cpp View File

@@ -9,7 +9,7 @@ TextField::TextField() {
} }


void TextField::draw(const widget::DrawContext &ctx) { 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; BNDwidgetState state;
if (this == APP->event->selectedWidget) 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) { void TextField::insertText(std::string text) {
if (cursor != selection) { if (cursor != selection) {
int begin = std::min(cursor, selection); int begin = std::min(cursor, selection);
@@ -194,12 +193,15 @@ void TextField::insertText(std::string text) {
onChange(eChange); onChange(eChange);
} }


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


void TextField::selectAll() { void TextField::selectAll() {


+ 22
- 0
src/widget/Widget.cpp View File

@@ -28,6 +28,28 @@ void Widget::setSize(math::Vec size) {
onResize(eResize); 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::Rect Widget::getChildrenBoundingBox() {
math::Vec min = math::Vec(INFINITY, INFINITY); math::Vec min = math::Vec(INFINITY, INFINITY);
math::Vec max = math::Vec(-INFINITY, -INFINITY); math::Vec max = math::Vec(-INFINITY, -INFINITY);


Loading…
Cancel
Save