Browse Source

Remove PulseAudio from default Linux rtaudio build, fix lastPath being overwritten when autosave is loaded, add List, replace AddModuleWindow with Sublime Text-like browser (partially complete)

tags/v0.6.0
Andrew Belt 6 years ago
parent
commit
e51ce44588
39 changed files with 417 additions and 436 deletions
  1. +1
    -1
      .github/ISSUE_TEMPLATE.md
  2. +2
    -2
      dep/Makefile
  3. +9
    -9
      include/app.hpp
  4. +4
    -1
      include/engine.hpp
  5. +18
    -23
      include/ui.hpp
  6. +1
    -0
      include/widgets.hpp
  7. +1
    -1
      src/Core/AudioInterface.cpp
  8. +1
    -1
      src/Core/Blank.cpp
  9. +1
    -1
      src/Core/Core.cpp
  10. +0
    -0
      src/Core/Core.hpp
  11. +1
    -1
      src/Core/MIDICCToCVInterface.cpp
  12. +1
    -1
      src/Core/MIDIToCVInterface.cpp
  13. +1
    -1
      src/Core/MIDITriggerToCVInterface.cpp
  14. +0
    -0
      src/Core/MidiClockToCV.cpp
  15. +0
    -0
      src/Core/MidiIO.cpp
  16. +0
    -0
      src/Core/MidiIO.hpp
  17. +1
    -1
      src/Core/Notes.cpp
  18. +1
    -1
      src/Core/QuadMIDIToCVInterface.cpp
  19. +0
    -292
      src/app/AddModuleWindow.cpp
  20. +10
    -5
      src/app/RackScene.cpp
  21. +9
    -9
      src/app/RackWidget.cpp
  22. +12
    -7
      src/app/Toolbar.cpp
  23. +3
    -4
      src/app/app.cpp
  24. +212
    -0
      src/app/moduleBrowser.cpp
  25. +2
    -2
      src/engine.cpp
  26. +5
    -3
      src/main.cpp
  27. +9
    -0
      src/settings.cpp
  28. +30
    -0
      src/ui/List.cpp
  29. +1
    -1
      src/ui/Menu.cpp
  30. +2
    -2
      src/ui/MenuItem.cpp
  31. +14
    -3
      src/ui/MenuOverlay.cpp
  32. +3
    -3
      src/ui/RadioButton.cpp
  33. +0
    -32
      src/ui/ScrollBar.cpp
  34. +51
    -12
      src/ui/ScrollWidget.cpp
  35. +9
    -4
      src/ui/TextField.cpp
  36. +2
    -2
      src/ui/WindowWidget.cpp
  37. +0
    -0
      src/ui/ui.cpp
  38. +0
    -11
      src/util.cpp
  39. +0
    -0
      src/widgets/widgets.cpp

+ 1
- 1
.github/ISSUE_TEMPLATE.md View File

@@ -2,7 +2,7 @@ Search before opening an issue to make sure your topic is not a duplicate. Delet

## For bug reports:

Operating system, or "all" if known to exist on all three:
Operating system(s):
Version if official Rack release, commit hash and/or branch if from source:

## For feature requests:


+ 2
- 2
dep/Makefile View File

@@ -145,7 +145,7 @@ ifeq ($(ARCH),win)
RTAUDIO_FLAGS += -DAUDIO_WINDOWS_DS=ON -DAUDIO_WINDOWS_WASAPI=ON -DAUDIO_WINDOWS_ASIO=ON
endif
ifeq ($(ARCH),lin)
RTAUDIO_FLAGS += -DAUDIO_LINUX_ALSA=ON -DAUDIO_LINUX_PULSE=ON
RTAUDIO_FLAGS += -DAUDIO_LINUX_ALSA=ON
endif

ifdef RTAUDIO_ALL_APIS
@@ -153,7 +153,7 @@ ifeq ($(ARCH),mac)
RTAUDIO_FLAGS += -DAUDIO_UNIX_JACK=ON
endif
ifeq ($(ARCH),lin)
RTAUDIO_FLAGS += -DAUDIO_LINUX_JACK=ON
RTAUDIO_FLAGS += -DAUDIO_LINUX_PULSE=ON -DAUDIO_LINUX_JACK=ON
endif
endif



+ 9
- 9
include/app.hpp View File

@@ -158,6 +158,8 @@ struct RackWidget : OpaqueWidget {
void openDialog();
void saveDialog();
void saveAsDialog();
/** If `lastPath` is defined, ask the user to reload it */
void revert();
void savePatch(std::string filename);
void loadPatch(std::string filename);
json_t *toJson();
@@ -171,6 +173,7 @@ struct RackWidget : OpaqueWidget {
bool requestModuleBox(ModuleWidget *m, Rect box);
/** Moves a module to the closest non-colliding position */
bool requestModuleBoxNearest(ModuleWidget *m, Rect box);

void step() override;
void draw(NVGcontext *vg) override;

@@ -183,13 +186,6 @@ struct RackRail : TransparentWidget {
void draw(NVGcontext *vg) override;
};

struct AddModuleWindow : Window {
Vec modulePos;

AddModuleWindow();
void step() override;
};

struct Panel : TransparentWidget {
NVGcolor backgroundColor;
std::shared_ptr<Image> backgroundImage;
@@ -524,8 +520,12 @@ extern RackScene *gRackScene;
extern RackWidget *gRackWidget;
extern Toolbar *gToolbar;

void sceneInit();
void sceneDestroy();
void appInit();
void appDestroy();
void appModuleBrowserCreate();
json_t *appModuleBrowserToJson();
void appModuleBrowserFromJson(json_t *root);


json_t *colorToJson(NVGcolor color);
NVGcolor jsonToColor(json_t *colorJ);


+ 4
- 1
include/engine.hpp View File

@@ -18,7 +18,10 @@ struct Light {
void setBrightness(float brightness) {
value = (brightness > 0.f) ? brightness * brightness : 0.f;
}
void setBrightnessSmooth(float brightness);
/** Emulates slow fall (but immediate rise) of LED brightness.
`frames` rescales the timestep. For example, if your module calls this method every 16 frames, use 16.0.
*/
void setBrightnessSmooth(float brightness, float frames = 1.f);
};

struct Input {


+ 18
- 23
include/ui.hpp View File

@@ -14,6 +14,11 @@ struct Label : Widget {
void draw(NVGcontext *vg) override;
};

struct List : OpaqueWidget {
void step() override;
void draw(NVGcontext *vg) override;
};

/** Deletes itself from parent when clicked */
struct MenuOverlay : OpaqueWidget {
void step() override;
@@ -33,7 +38,7 @@ struct Menu : OpaqueWidget {
box.size = Vec(0, 0);
}
~Menu();
// Resizes menu and calls addChild()
/** Deprecated. Just use addChild(child) instead */
void pushChild(Widget *child) DEPRECATED {
addChild(child);
}
@@ -44,10 +49,6 @@ struct Menu : OpaqueWidget {
};

struct MenuEntry : OpaqueWidget {
MenuEntry() {
box.size = Vec(0, BND_WIDGET_HEIGHT);
}

template <typename T = MenuEntry>
static T *create() {
T *o = Widget::create<T>(Vec());
@@ -57,6 +58,9 @@ struct MenuEntry : OpaqueWidget {

struct MenuLabel : MenuEntry {
std::string text;
MenuLabel() {
box.size = Vec(0, BND_WIDGET_HEIGHT);
}
void draw(NVGcontext *vg) override;
void step() override;

@@ -71,6 +75,9 @@ struct MenuLabel : MenuEntry {
struct MenuItem : MenuEntry {
std::string text;
std::string rightText;
MenuItem() {
box.size = Vec(0, BND_WIDGET_HEIGHT);
}
void draw(NVGcontext *vg) override;
void step() override;
virtual Menu *createChildMenu() {return NULL;}
@@ -89,7 +96,7 @@ struct MenuItem : MenuEntry {
struct WindowOverlay : OpaqueWidget {
};

struct Window : OpaqueWidget {
struct WindowWidget : OpaqueWidget {
std::string title;
void draw(NVGcontext *vg) override;
void onDragMove(EventDragMove &e) override;
@@ -139,22 +146,7 @@ struct Slider : OpaqueWidget, QuantityWidget {
void onMouseDown(EventMouseDown &e) override;
};

/** Parent must be a ScrollWidget */
struct ScrollBar : OpaqueWidget {
enum { VERTICAL, HORIZONTAL } orientation;
BNDwidgetState state = BND_DEFAULT;
float offset = 0.0;
float size = 0.0;

ScrollBar() {
box.size = Vec(BND_SCROLLBAR_WIDTH, BND_SCROLLBAR_HEIGHT);
}
void draw(NVGcontext *vg) override;
void onDragStart(EventDragStart &e) override;
void onDragMove(EventDragMove &e) override;
void onDragEnd(EventDragEnd &e) override;
};

struct ScrollBar;
/** Handles a container with ScrollBar */
struct ScrollWidget : OpaqueWidget {
Widget *container;
@@ -186,7 +178,10 @@ struct TextField : OpaqueWidget {
void onFocus(EventFocus &e) override;
void onText(EventText &e) override;
void onKey(EventKey &e) override;
void insertText(std::string newText);
/** Inserts text at the cursor, replacing the selection if necessary */
void insertText(std::string text);
/** Replaces the entire text */
void setText(std::string text);
virtual int getTextPosition(Vec mousePos);
virtual void onTextChange() {}
};


+ 1
- 0
include/widgets.hpp View File

@@ -257,6 +257,7 @@ struct FramebufferWidget : virtual Widget {
void onZoom(EventZoom &e) override;
};

/** A Widget representing a float value */
struct QuantityWidget : virtual Widget {
float value = 0.0;
float minValue = 0.0;


src/core/AudioInterface.cpp → src/Core/AudioInterface.cpp View File

@@ -4,7 +4,7 @@
#include <thread>
#include <mutex>
#include <condition_variable>
#include "core.hpp"
#include "Core.hpp"
#include "audio.hpp"
#include "dsp/samplerate.hpp"
#include "dsp/ringbuffer.hpp"

src/core/Blank.cpp → src/Core/Blank.cpp View File

@@ -1,4 +1,4 @@
#include "core.hpp"
#include "Core.hpp"

using namespace rack;


src/core/core.cpp → src/Core/Core.cpp View File

@@ -1,4 +1,4 @@
#include "core.hpp"
#include "Core.hpp"


void init(rack::Plugin *p) {

src/core/core.hpp → src/Core/Core.hpp View File


src/core/MIDICCToCVInterface.cpp → src/Core/MIDICCToCVInterface.cpp View File

@@ -1,4 +1,4 @@
#include "core.hpp"
#include "Core.hpp"
#include "midi.hpp"
#include "dsp/filter.hpp"


src/core/MIDIToCVInterface.cpp → src/Core/MIDIToCVInterface.cpp View File

@@ -1,4 +1,4 @@
#include "core.hpp"
#include "Core.hpp"
#include "midi.hpp"
#include "dsp/filter.hpp"


src/core/MIDITriggerToCVInterface.cpp → src/Core/MIDITriggerToCVInterface.cpp View File

@@ -1,4 +1,4 @@
#include "core.hpp"
#include "Core.hpp"
#include "midi.hpp"
#include "dsp/filter.hpp"


src/core/MidiClockToCV.cpp → src/Core/MidiClockToCV.cpp View File


src/core/MidiIO.cpp → src/Core/MidiIO.cpp View File


src/core/MidiIO.hpp → src/Core/MidiIO.hpp View File


src/core/Notes.cpp → src/Core/Notes.cpp View File

@@ -1,4 +1,4 @@
#include "core.hpp"
#include "Core.hpp"

using namespace rack;


src/core/QuadMIDIToCVInterface.cpp → src/Core/QuadMIDIToCVInterface.cpp View File

@@ -1,4 +1,4 @@
#include "core.hpp"
#include "Core.hpp"
#include "midi.hpp"



+ 0
- 292
src/app/AddModuleWindow.cpp View File

@@ -1,292 +0,0 @@
#include "app.hpp"
#include "plugin.hpp"
#include <thread>
#include <set>
#include <algorithm>


namespace rack {


static std::string sManufacturer;
static Model *sModel = NULL;
static std::string sFilter;


struct ListMenu : OpaqueWidget {
void draw(NVGcontext *vg) override {
Widget::draw(vg);
}

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

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


struct UrlItem : MenuItem {
std::string url;
void onAction(EventAction &e) override {
std::thread t(openBrowser, url);
t.detach();
}
};


struct MetadataMenu : ListMenu {
Model *model = NULL;

void step() override {
if (model != sModel) {
model = sModel;
clearChildren();

if (model) {
// Tag list
if (!model->tags.empty()) {
for (ModelTag tag : model->tags) {
addChild(construct<MenuLabel>(&MenuLabel::text, gTagNames[tag]));
}
addChild(construct<MenuEntry>());
}

// Plugin name
std::string pluginName = model->plugin->slug;
if (!model->plugin->version.empty()) {
pluginName += " v";
pluginName += model->plugin->version;
}
addChild(construct<MenuLabel>(&MenuLabel::text, pluginName));

// Plugin metadata
if (!model->plugin->website.empty()) {
addChild(construct<UrlItem>(&MenuItem::text, "Website", &UrlItem::url, model->plugin->website));
}
if (!model->plugin->manual.empty()) {
addChild(construct<UrlItem>(&MenuItem::text, "Manual", &UrlItem::url, model->plugin->manual));
}
if (!model->plugin->path.empty()) {
addChild(construct<UrlItem>(&MenuItem::text, "Browse directory", &UrlItem::url, model->plugin->path));
}
}
}

ListMenu::step();
}
};


static bool isModelMatch(Model *model, std::string search) {
// Build content string
std::string str;
str += model->manufacturer;
str += " ";
str += model->name;
str += " ";
str += model->slug;
for (ModelTag tag : model->tags) {
str += " ";
str += gTagNames[tag];
}
str = lowercase(str);
search = lowercase(search);
return (str.find(search) != std::string::npos);
}


struct ModelItem : MenuItem {
Model *model;
void onAction(EventAction &e) override {
ModuleWidget *moduleWidget = model->createModuleWidget();
gRackWidget->moduleContainer->addChild(moduleWidget);
// Move module nearest to the mouse position
Rect box;
box.size = moduleWidget->box.size;
AddModuleWindow *w = getAncestorOfType<AddModuleWindow>();
box.pos = w->modulePos.minus(box.getCenter());
gRackWidget->requestModuleBoxNearest(moduleWidget, box);
}
void onMouseEnter(EventMouseEnter &e) override {
sModel = model;
MenuItem::onMouseEnter(e);
}
};


struct ModelMenu : ListMenu {
std::string manufacturer;
std::string filter;

void step() override {
if (manufacturer != sManufacturer) {
manufacturer = sManufacturer;
filter = "";
clearChildren();
addChild(construct<MenuLabel>(&MenuLabel::text, manufacturer));
// Add models for the selected manufacturer
for (Plugin *plugin : gPlugins) {
for (Model *model : plugin->models) {
if (model->manufacturer == manufacturer) {
addChild(construct<ModelItem>(&MenuItem::text, model->name, &ModelItem::model, model));
}
}
}
}

if (filter != sFilter) {
filter = sFilter;
// Make all children invisible
for (Widget *child : children) {
child->visible = false;
}
// Make children with a matching model visible
for (Widget *child : children) {
ModelItem *item = dynamic_cast<ModelItem*>(child);
if (!item)
continue;

if (isModelMatch(item->model, filter)) {
item->visible = true;
}
}
}

ListMenu::step();
}
};


struct ManufacturerItem : MenuItem {
Model *model;
void onAction(EventAction &e) override {
sManufacturer = text;
e.consumed = false;
}
};


struct ManufacturerMenu : ListMenu {
std::string filter;

ManufacturerMenu() {
addChild(construct<MenuLabel>(&MenuLabel::text, "Manufacturers"));

// Collect manufacturer names
std::set<std::string> manufacturers;
for (Plugin *plugin : gPlugins) {
for (Model *model : plugin->models) {
manufacturers.insert(model->manufacturer);
}
}
// Add menu item for each manufacturer name
for (std::string manufacturer : manufacturers) {
addChild(construct<ManufacturerItem>(&MenuItem::text, manufacturer));
}
}

void step() override {
if (filter != sFilter) {
// Make all children invisible
for (Widget *child : children) {
child->visible = false;
}
// Make children with a matching model visible
for (Widget *child : children) {
MenuItem *item = dynamic_cast<MenuItem*>(child);
if (!item)
continue;

std::string manufacturer = item->text;
for (Plugin *plugin : gPlugins) {
for (Model *model : plugin->models) {
if (model->manufacturer == manufacturer) {
if (isModelMatch(model, sFilter)) {
item->visible = true;
}
}
}
}
}
filter = sFilter;
}

ListMenu::step();
}
};


struct SearchModuleField : TextField {
void onTextChange() override {
sFilter = text;
}
};


AddModuleWindow::AddModuleWindow() {
box.size = Vec(600, 300);
title = "Add module";

float posY = BND_NODE_TITLE_HEIGHT;

// Search
SearchModuleField *searchField = new SearchModuleField();
searchField->box.pos.y = posY;
posY += searchField->box.size.y;
searchField->box.size.x = box.size.x;
searchField->text = sFilter;
gFocusedWidget = searchField;
{
EventFocus eFocus;
searchField->onFocus(eFocus);
searchField->onTextChange();
}
addChild(searchField);

// Manufacturers
ManufacturerMenu *manufacturerMenu = new ManufacturerMenu();
manufacturerMenu->box.size.x = 200;

ScrollWidget *manufacturerScroll = new ScrollWidget();
manufacturerScroll->container->addChild(manufacturerMenu);
manufacturerScroll->box.pos = Vec(0, posY);
manufacturerScroll->box.size = Vec(200, box.size.y - posY);
addChild(manufacturerScroll);

// Models
ModelMenu *modelMenu = new ModelMenu();
modelMenu->box.size.x = 200;

ScrollWidget *modelScroll = new ScrollWidget();
modelScroll->container->addChild(modelMenu);
modelScroll->box.pos = Vec(200, posY);
modelScroll->box.size = Vec(200, box.size.y - posY);
addChild(modelScroll);

// Metadata
MetadataMenu *metadataMenu = new MetadataMenu();
metadataMenu->box.size.x = 200;

ScrollWidget *metadataScroll = new ScrollWidget();
metadataScroll->container->addChild(metadataMenu);
metadataScroll->box.pos = Vec(400, posY);
metadataScroll->box.size = Vec(200, box.size.y - posY);
addChild(metadataScroll);
}


void AddModuleWindow::step() {
Widget::step();
}


} // namespace rack

+ 10
- 5
src/app/RackScene.cpp View File

@@ -94,35 +94,40 @@ void RackScene::onHoverKey(EventHoverKey &e) {
if (windowIsModPressed() && !windowIsShiftPressed()) {
gRackWidget->reset();
e.consumed = true;
return;
}
} break;
case GLFW_KEY_Q: {
if (windowIsModPressed() && !windowIsShiftPressed()) {
windowClose();
e.consumed = true;
return;
}
} break;
case GLFW_KEY_O: {
if (windowIsModPressed() && !windowIsShiftPressed()) {
gRackWidget->openDialog();
e.consumed = true;
return;
}
} break;
case GLFW_KEY_S: {
if (windowIsModPressed() && !windowIsShiftPressed()) {
gRackWidget->saveDialog();
e.consumed = true;
return;
}
if (windowIsModPressed() && windowIsShiftPressed()) {
gRackWidget->saveAsDialog();
e.consumed = true;
return;
}
} break;
case GLFW_KEY_R: {
if (windowIsModPressed() && !windowIsShiftPressed()) {
gRackWidget->revert();
e.consumed = true;
}
} break;
case GLFW_KEY_ENTER: {
appModuleBrowserCreate();
e.consumed = true;
} break;
}
}
}


+ 9
- 9
src/app/RackWidget.cpp View File

@@ -141,6 +141,14 @@ void RackWidget::loadPatch(std::string path) {
fclose(file);
}

void RackWidget::revert() {
if (lastPath.empty())
return;
if (osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, "Revert your patch to the last saved state?")) {
loadPatch(lastPath);
}
}

json_t *RackWidget::toJson() {
// root
json_t *rootJ = json_object();
@@ -438,15 +446,7 @@ void RackWidget::onMouseDown(EventMouseDown &e) {
return;

if (e.button == 1) {
MenuOverlay *overlay = new MenuOverlay();

AddModuleWindow *window = new AddModuleWindow();
// Set center position
window->box.pos = gMousePos.minus(window->box.getCenter());
window->modulePos = lastMousePos;

overlay->addChild(window);
gScene->setOverlay(overlay);
appModuleBrowserCreate();
}
e.consumed = true;
e.target = this;


+ 12
- 7
src/app/Toolbar.cpp View File

@@ -30,6 +30,12 @@ struct SaveAsItem : MenuItem {
}
};

struct RevertItem : MenuItem {
void onAction(EventAction &e) override {
gRackWidget->revert();
}
};

struct QuitItem : MenuItem {
void onAction(EventAction &e) override {
windowClose();
@@ -42,13 +48,12 @@ struct FileChoice : ChoiceButton {
menu->box.pos = getAbsoluteOffset(Vec(0, box.size.y));
menu->box.size.x = box.size.x;

{
menu->addChild(construct<NewItem>(&MenuItem::text, "New", &MenuItem::rightText, WINDOW_MOD_KEY_NAME "+N"));
menu->addChild(construct<OpenItem>(&MenuItem::text, "Open", &MenuItem::rightText, WINDOW_MOD_KEY_NAME "+O"));
menu->addChild(construct<SaveItem>(&MenuItem::text, "Save", &MenuItem::rightText, WINDOW_MOD_KEY_NAME "+S"));
menu->addChild(construct<SaveAsItem>(&MenuItem::text, "Save as", &MenuItem::rightText, WINDOW_MOD_KEY_NAME "+Shift+S"));
menu->addChild(construct<QuitItem>(&MenuItem::text, "Quit", &MenuItem::rightText, WINDOW_MOD_KEY_NAME "+Q"));
}
menu->addChild(construct<NewItem>(&MenuItem::text, "New", &MenuItem::rightText, WINDOW_MOD_KEY_NAME "+N"));
menu->addChild(construct<OpenItem>(&MenuItem::text, "Open", &MenuItem::rightText, WINDOW_MOD_KEY_NAME "+O"));
menu->addChild(construct<SaveItem>(&MenuItem::text, "Save", &MenuItem::rightText, WINDOW_MOD_KEY_NAME "+S"));
menu->addChild(construct<SaveAsItem>(&MenuItem::text, "Save as", &MenuItem::rightText, WINDOW_MOD_KEY_NAME "+Shift+S"));
menu->addChild(construct<RevertItem>(&MenuItem::text, "Revert", &MenuItem::rightText, WINDOW_MOD_KEY_NAME "+R"));
menu->addChild(construct<QuitItem>(&MenuItem::text, "Quit", &MenuItem::rightText, WINDOW_MOD_KEY_NAME "+Q"));
}
};



src/app.cpp → src/app/app.cpp View File

@@ -13,14 +13,13 @@ Toolbar *gToolbar = NULL;
RackScene *gRackScene = NULL;


void sceneInit() {
void appInit() {
gRackScene = new RackScene();
gScene = gRackScene;
}

void sceneDestroy() {
delete gScene;
gScene = NULL;
void appDestroy() {
delete gRackScene;
}



+ 212
- 0
src/app/moduleBrowser.cpp View File

@@ -0,0 +1,212 @@
#include "app.hpp"
#include "plugin.hpp"
#include "window.hpp"
#include <set>


#define BND_LABEL_FONT_SIZE 13


namespace rack {


static std::string sSearch;
static std::set<Model*> sFavoriteModels;


struct FavoriteRadioButton : RadioButton {
Model *model = NULL;
void onChange(EventChange &e) override {
debug("HI");
if (!model)
return;
if (value) {
sFavoriteModels.insert(model);
}
else {
auto it = sFavoriteModels.find(model);
if (it != sFavoriteModels.end())
sFavoriteModels.erase(it);
}
}
};


struct ModuleListItem : OpaqueWidget {
bool selected = false;
FavoriteRadioButton *favoriteButton;

ModuleListItem() {
box.size.y = 3 * BND_WIDGET_HEIGHT;

favoriteButton = new FavoriteRadioButton();
favoriteButton->box.pos = Vec(7, BND_WIDGET_HEIGHT);
favoriteButton->box.size.x = 20;
favoriteButton->label = "★";
addChild(favoriteButton);
}

void draw(NVGcontext *vg) override {
BNDwidgetState state = selected ? BND_HOVER : BND_DEFAULT;
bndMenuItem(vg, 0.0, 0.0, box.size.x, box.size.y, state, -1, "");
Widget::draw(vg);
}

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

EventAction eAction;
eAction.consumed = true;
onAction(eAction);
if (eAction.consumed) {
// deletes `this`
gScene->setOverlay(NULL);
}
}
};

struct ModelItem : ModuleListItem {
Model *model;

void setModel(Model *model) {
this->model = model;
auto it = sFavoriteModels.find(model);
if (it != sFavoriteModels.end())
favoriteButton->setValue(1);
favoriteButton->model = model;
}

void draw(NVGcontext *vg) override {
ModuleListItem::draw(vg);

// bndMenuItem(vg, 0.0, 0.0, box.size.x, box.size.y, BND_DEFAULT, -1, model->name.c_str());

float x = box.size.x - bndLabelWidth(vg, -1, model->manufacturer.c_str());
NVGcolor rightColor = bndGetTheme()->menuTheme.textColor;
bndIconLabelValue(vg, x, 0.0, box.size.x, box.size.y, -1, rightColor, BND_LEFT, BND_LABEL_FONT_SIZE, model->manufacturer.c_str(), NULL);
}

void onAction(EventAction &e) override {
ModuleWidget *moduleWidget = model->createModuleWidget();
gRackWidget->moduleContainer->addChild(moduleWidget);
// Move module nearest to the mouse position
// Rect box;
// box.size = moduleWidget->box.size;
// AddModuleWindow *w = getAncestorOfType<AddModuleWindow>();
// box.pos = w->modulePos.minus(box.getCenter());
// gRackWidget->requestModuleBoxNearest(moduleWidget, box);
}
};


struct ModuleBrowser;

struct SearchModuleField : TextField {
ModuleBrowser *moduleBrowser;
void onTextChange() override;
void onKey(EventKey &e) override;
};


struct ModuleBrowser : OpaqueWidget {
SearchModuleField *searchField;
ScrollWidget *moduleScroll;
List *moduleList;

ModuleBrowser() {
box.size.x = 300;

// Search
searchField = new SearchModuleField();
searchField->box.size.x = box.size.x;
searchField->moduleBrowser = this;
addChild(searchField);

moduleList = new List();
moduleList->box.size = Vec(box.size.x, 0.0);

// Module Scroll
moduleScroll = new ScrollWidget();
moduleScroll->box.pos.y = searchField->box.size.y;
moduleScroll->box.size.x = box.size.x;
moduleScroll->container->addChild(moduleList);
addChild(moduleScroll);

// Focus search
searchField->setText(sSearch);
EventFocus eFocus;
searchField->onFocus(eFocus);
}

void setSearch(std::string search) {
moduleList->clearChildren();

// Favorites
for (Model *model : sFavoriteModels) {
ModelItem *item = new ModelItem();
item->setModel(model);
moduleList->addChild(item);
}

// Models
for (Plugin *plugin : gPlugins) {
for (Model *model : plugin->models) {
ModelItem *item = new ModelItem();
item->setModel(model);
moduleList->addChild(item);
}
}
}

void step() override {
box.pos = parent->box.size.minus(box.size).div(2).round();
box.pos.y = 40;
box.size.y = parent->box.size.y - 2 * box.pos.y;

moduleScroll->box.size.y = box.size.y - moduleScroll->box.pos.y;
gFocusedWidget = searchField;
Widget::step();
}
};


void SearchModuleField::onTextChange() {
sSearch = text;
moduleBrowser->setSearch(text);
}

void SearchModuleField::onKey(EventKey &e) {
switch (e.key) {
case GLFW_KEY_ESCAPE: {
gScene->setOverlay(NULL);
e.consumed = true;
return;
} break;
}

if (!e.consumed) {
TextField::onKey(e);
}
}


void appModuleBrowserCreate() {
MenuOverlay *overlay = new MenuOverlay();

ModuleBrowser *moduleBrowser = new ModuleBrowser();
overlay->addChild(moduleBrowser);
gScene->setOverlay(overlay);
}

json_t *appModuleBrowserToJson() {
// TODO
return json_object();
}

void appModuleBrowserFromJson(json_t *root) {
// TODO
}


} // namespace rack

+ 2
- 2
src/engine.cpp View File

@@ -38,11 +38,11 @@ float Light::getBrightness() {
return sqrtf(fmaxf(0.f, value));
}

void Light::setBrightnessSmooth(float brightness) {
void Light::setBrightnessSmooth(float brightness, float frames) {
float v = (brightness > 0.f) ? brightness * brightness : 0.f;
if (v < value) {
// Fade out light with lambda = framerate
value += (v - value) * sampleTime * (60.f * 1.f);
value += (v - value) * sampleTime * frames * 60.f;
}
else {
// Immediately illuminate light


+ 5
- 3
src/main.cpp View File

@@ -34,20 +34,22 @@ int main(int argc, char* argv[]) {
pluginInit();
engineInit();
windowInit();
sceneInit();
appInit();
settingsLoad(assetLocal("settings.json"));
std::string oldLastPath = gRackWidget->lastPath;

// To prevent launch crashes, if Rack crashes between now and 15 seconds from now, the "skipAutosaveOnLaunch" property will remain in settings.json, so that in the next launch, the broken autosave will not be loaded.
bool oldSkipAutosaveOnLaunch = skipAutosaveOnLaunch;
skipAutosaveOnLaunch = true;
settingsSave(assetLocal("settings.json"));
skipAutosaveOnLaunch = false;
if (oldSkipAutosaveOnLaunch && osdialog_message(OSDIALOG_INFO, OSDIALOG_YES_NO, "Rack has recovered from a crash, likely caused by a faulty module in your patch. Would you like to clear your patch and start over?")) {
if (oldSkipAutosaveOnLaunch && osdialog_message(OSDIALOG_INFO, OSDIALOG_YES_NO, "Rack has recovered from a crash, possibly caused by a faulty module in your patch. Would you like to clear your patch and start over?")) {
// Do nothing. Empty patch is already loaded.
}
else {
gRackWidget->loadPatch(assetLocal("autosave.vcv"));
}
gRackWidget->lastPath = oldLastPath;

engineStart();
windowRun();
@@ -55,7 +57,7 @@ int main(int argc, char* argv[]) {

gRackWidget->savePatch(assetLocal("autosave.vcv"));
settingsSave(assetLocal("settings.json"));
sceneDestroy();
appDestroy();
windowDestroy();
engineDestroy();
pluginDestroy();


+ 9
- 0
src/settings.cpp View File

@@ -64,6 +64,9 @@ static json_t *settingsToJson() {
json_object_set_new(rootJ, "skipAutosaveOnLaunch", json_true());
}

// moduleBrowser
json_object_set_new(rootJ, "moduleBrowser", appModuleBrowserToJson());

return rootJ;
}

@@ -123,9 +126,15 @@ static void settingsFromJson(json_t *rootJ) {
if (lastPathJ)
gRackWidget->lastPath = json_string_value(lastPathJ);

// skipAutosaveOnLaunch
json_t *skipAutosaveOnLaunchJ = json_object_get(rootJ, "skipAutosaveOnLaunch");
if (skipAutosaveOnLaunchJ)
skipAutosaveOnLaunch = json_boolean_value(skipAutosaveOnLaunchJ);

// moduleBrowser
json_t * moduleBrowserJ = json_object_get(rootJ, "moduleBrowser");
if (moduleBrowserJ)
appModuleBrowserFromJson(moduleBrowserJ);
}




+ 30
- 0
src/ui/List.cpp View File

@@ -0,0 +1,30 @@
#include "ui.hpp"


namespace rack {


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

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

void List::draw(NVGcontext *vg) {
bndBackground(vg, 0.0, 0.0, box.size.x, box.size.y);
bndBevel(vg, 0.0, 0.0, box.size.x, box.size.y);
Widget::draw(vg);
}


} // namespace rack

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

@@ -29,7 +29,7 @@ void Menu::step() {
for (Widget *child : children) {
if (!child->visible)
continue;
// Increase height, set position of child
// Increment height, set position of child
child->box.pos = Vec(0, box.size.y);
box.size.y += child->box.size.y;
// Increase width based on maximum width of child


+ 2
- 2
src/ui/MenuItem.cpp View File

@@ -24,7 +24,7 @@ void MenuItem::draw(NVGcontext *vg) {
}

void MenuItem::step() {
// Add 10 more pixels because Retina measurements are sometimes too small
// Add 10 more pixels because measurements on high-DPI screens are sometimes too small for some reason
const float rightPadding = 10.0;
// HACK use gVg from the window.
// All this does is inspect the font, so it shouldn't modify gVg and should work when called from a FramebufferWidget for example.
@@ -53,7 +53,7 @@ void MenuItem::onDragDrop(EventDragDrop &e) {
return;

EventAction eAction;
// TODO Perhaps remove this? It would require all onAction() methods to call this explicitly, which might be too annoying to change.
// Consume event by default, but allow action to un-consume it to prevent the menu from being removed.
eAction.consumed = true;
onAction(eAction);
if (eAction.consumed) {


+ 14
- 3
src/ui/MenuOverlay.cpp View File

@@ -1,4 +1,5 @@
#include "ui.hpp"
#include "window.hpp"


namespace rack {
@@ -23,9 +24,19 @@ void MenuOverlay::onMouseDown(EventMouseDown &e) {
}

void MenuOverlay::onHoverKey(EventHoverKey &e) {
// Recurse children but consume the event
Widget::onHoverKey(e);
e.consumed = true;
switch (e.key) {
case GLFW_KEY_ESCAPE: {
gScene->setOverlay(NULL);
e.consumed = true;
return;
} break;
}

if (!e.consumed) {
// Recurse children but consume the event
Widget::onHoverKey(e);
e.consumed = true;
}
}




+ 3
- 3
src/ui/RadioButton.cpp View File

@@ -17,10 +17,10 @@ void RadioButton::onMouseLeave(EventMouseLeave &e) {

void RadioButton::onDragDrop(EventDragDrop &e) {
if (e.origin == this) {
if (value == 0.0)
value = 1.0;
if (value)
setValue(0.0);
else
value = 0.0;
setValue(1.0);

EventAction eAction;
onAction(eAction);


+ 0
- 32
src/ui/ScrollBar.cpp View File

@@ -1,32 +0,0 @@
#include "ui.hpp"
#include "window.hpp"


namespace rack {


void ScrollBar::draw(NVGcontext *vg) {
bndScrollBar(vg, 0.0, 0.0, box.size.x, box.size.y, state, offset, size);
}

void ScrollBar::onDragStart(EventDragStart &e) {
state = BND_ACTIVE;
windowCursorLock();
}

void ScrollBar::onDragMove(EventDragMove &e) {
ScrollWidget *scrollWidget = dynamic_cast<ScrollWidget*>(parent);
assert(scrollWidget);
if (orientation == HORIZONTAL)
scrollWidget->offset.x += e.mouseRel.x;
else
scrollWidget->offset.y += e.mouseRel.y;
}

void ScrollBar::onDragEnd(EventDragEnd &e) {
state = BND_DEFAULT;
windowCursorUnlock();
}


} // namespace rack

+ 51
- 12
src/ui/ScrollWidget.cpp View File

@@ -4,6 +4,47 @@

namespace rack {


/** Parent must be a ScrollWidget */
struct ScrollBar : OpaqueWidget {
enum Orientation {
VERTICAL,
HORIZONTAL
};
Orientation orientation;
BNDwidgetState state = BND_DEFAULT;
float offset = 0.0;
float size = 0.0;

ScrollBar() {
box.size = Vec(BND_SCROLLBAR_WIDTH, BND_SCROLLBAR_HEIGHT);
}

void draw(NVGcontext *vg) override {
bndScrollBar(vg, 0.0, 0.0, box.size.x, box.size.y, state, offset, size);
}

void onDragStart(EventDragStart &e) override {
state = BND_ACTIVE;
windowCursorLock();
}

void onDragMove(EventDragMove &e) override {
ScrollWidget *scrollWidget = dynamic_cast<ScrollWidget*>(parent);
assert(scrollWidget);
if (orientation == HORIZONTAL)
scrollWidget->offset.x += e.mouseRel.x;
else
scrollWidget->offset.y += e.mouseRel.y;
}

void onDragEnd(EventDragEnd &e) override {
state = BND_DEFAULT;
windowCursorUnlock();
}
};


ScrollWidget::ScrollWidget() {
container = new Widget();
addChild(container);
@@ -21,9 +62,7 @@ ScrollWidget::ScrollWidget() {

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

Widget::draw(vg);

nvgResetScissor(vg);
}

@@ -32,13 +71,6 @@ void ScrollWidget::step() {
Vec containerCorner = container->getChildrenBoundingBox().getBottomRight();
offset = offset.clamp(Rect(Vec(0, 0), containerCorner.minus(box.size)));

// Resize scroll bars
Vec inner = Vec(box.size.x - verticalScrollBar->box.size.x, box.size.y - horizontalScrollBar->box.size.y);
horizontalScrollBar->box.pos.y = inner.y;
horizontalScrollBar->box.size.x = inner.x;
verticalScrollBar->box.pos.x = inner.x;
verticalScrollBar->box.size.y = inner.y;

// Update the container's positions from the offset
container->box.pos = offset.neg().round();

@@ -47,12 +79,19 @@ void ScrollWidget::step() {
Vec scrollbarOffset = offset.div(viewportSize.minus(box.size));
Vec scrollbarSize = box.size.div(viewportSize);

horizontalScrollBar->offset = scrollbarOffset.x;
horizontalScrollBar->size = scrollbarSize.x;
horizontalScrollBar->visible = (0.0 < scrollbarSize.x && scrollbarSize.x < 1.0);
verticalScrollBar->visible = (0.0 < scrollbarSize.y && scrollbarSize.y < 1.0);
horizontalScrollBar->offset = scrollbarOffset.x;
verticalScrollBar->offset = scrollbarOffset.y;
horizontalScrollBar->size = scrollbarSize.x;
verticalScrollBar->size = scrollbarSize.y;
verticalScrollBar->visible = (0.0 < scrollbarSize.y && scrollbarSize.y < 1.0);

// Resize scroll bars
Vec inner = Vec(box.size.x - verticalScrollBar->box.size.x, box.size.y - horizontalScrollBar->box.size.y);
horizontalScrollBar->box.pos.y = inner.y;
verticalScrollBar->box.pos.x = inner.x;
horizontalScrollBar->box.size.x = verticalScrollBar->visible ? inner.x : box.size.x;
verticalScrollBar->box.size.y = horizontalScrollBar->visible ? inner.y : box.size.y;

Widget::step();
}


+ 9
- 4
src/ui/TextField.cpp View File

@@ -155,15 +155,20 @@ void TextField::onKey(EventKey &e) {
e.consumed = true;
}

void TextField::insertText(std::string newText) {
void TextField::insertText(std::string text) {
if (begin < end)
text.erase(begin, end - begin);
text.insert(begin, newText);
begin += newText.size();
this->text.erase(begin, end - begin);
this->text.insert(begin, text);
begin += text.size();
end = begin;
onTextChange();
}

void TextField::setText(std::string text) {
this->text = text;
onTextChange();
}

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


src/ui/Window.cpp → src/ui/WindowWidget.cpp View File

@@ -4,12 +4,12 @@
namespace rack {


void Window::draw(NVGcontext *vg) {
void WindowWidget::draw(NVGcontext *vg) {
bndNodeBackground(vg, 0.0, 0.0, box.size.x, box.size.y, BND_DEFAULT, -1, title.c_str(), bndGetTheme()->backgroundColor);
Widget::draw(vg);
}

void Window::onDragMove(EventDragMove &e) {
void WindowWidget::onDragMove(EventDragMove &e) {
box.pos = box.pos.plus(e.mouseRel);
}


src/ui.cpp → src/ui/ui.cpp View File


+ 0
- 11
src/util.cpp View File

@@ -1,11 +0,0 @@
#include <stdarg.h>
#include <string.h>
#include <random>
#include <algorithm>
#include <libgen.h> // for dirname and basename
#include <sys/time.h>

#if ARCH_WIN
#include <windows.h>
#include <shellapi.h>
#endif

src/widgets.cpp → src/widgets/widgets.cpp View File


Loading…
Cancel
Save