@@ -2,7 +2,7 @@ Search before opening an issue to make sure your topic is not a duplicate. Delet | |||||
## For bug reports: | ## 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: | Version if official Rack release, commit hash and/or branch if from source: | ||||
## For feature requests: | ## For feature requests: | ||||
@@ -145,7 +145,7 @@ ifeq ($(ARCH),win) | |||||
RTAUDIO_FLAGS += -DAUDIO_WINDOWS_DS=ON -DAUDIO_WINDOWS_WASAPI=ON -DAUDIO_WINDOWS_ASIO=ON | RTAUDIO_FLAGS += -DAUDIO_WINDOWS_DS=ON -DAUDIO_WINDOWS_WASAPI=ON -DAUDIO_WINDOWS_ASIO=ON | ||||
endif | endif | ||||
ifeq ($(ARCH),lin) | ifeq ($(ARCH),lin) | ||||
RTAUDIO_FLAGS += -DAUDIO_LINUX_ALSA=ON -DAUDIO_LINUX_PULSE=ON | |||||
RTAUDIO_FLAGS += -DAUDIO_LINUX_ALSA=ON | |||||
endif | endif | ||||
ifdef RTAUDIO_ALL_APIS | ifdef RTAUDIO_ALL_APIS | ||||
@@ -153,7 +153,7 @@ ifeq ($(ARCH),mac) | |||||
RTAUDIO_FLAGS += -DAUDIO_UNIX_JACK=ON | RTAUDIO_FLAGS += -DAUDIO_UNIX_JACK=ON | ||||
endif | endif | ||||
ifeq ($(ARCH),lin) | ifeq ($(ARCH),lin) | ||||
RTAUDIO_FLAGS += -DAUDIO_LINUX_JACK=ON | |||||
RTAUDIO_FLAGS += -DAUDIO_LINUX_PULSE=ON -DAUDIO_LINUX_JACK=ON | |||||
endif | endif | ||||
endif | endif | ||||
@@ -158,6 +158,8 @@ struct RackWidget : OpaqueWidget { | |||||
void openDialog(); | void openDialog(); | ||||
void saveDialog(); | void saveDialog(); | ||||
void saveAsDialog(); | void saveAsDialog(); | ||||
/** If `lastPath` is defined, ask the user to reload it */ | |||||
void revert(); | |||||
void savePatch(std::string filename); | void savePatch(std::string filename); | ||||
void loadPatch(std::string filename); | void loadPatch(std::string filename); | ||||
json_t *toJson(); | json_t *toJson(); | ||||
@@ -171,6 +173,7 @@ struct RackWidget : OpaqueWidget { | |||||
bool requestModuleBox(ModuleWidget *m, Rect box); | bool requestModuleBox(ModuleWidget *m, Rect box); | ||||
/** Moves a module to the closest non-colliding position */ | /** Moves a module to the closest non-colliding position */ | ||||
bool requestModuleBoxNearest(ModuleWidget *m, Rect box); | bool requestModuleBoxNearest(ModuleWidget *m, Rect box); | ||||
void step() override; | void step() override; | ||||
void draw(NVGcontext *vg) override; | void draw(NVGcontext *vg) override; | ||||
@@ -183,13 +186,6 @@ struct RackRail : TransparentWidget { | |||||
void draw(NVGcontext *vg) override; | void draw(NVGcontext *vg) override; | ||||
}; | }; | ||||
struct AddModuleWindow : Window { | |||||
Vec modulePos; | |||||
AddModuleWindow(); | |||||
void step() override; | |||||
}; | |||||
struct Panel : TransparentWidget { | struct Panel : TransparentWidget { | ||||
NVGcolor backgroundColor; | NVGcolor backgroundColor; | ||||
std::shared_ptr<Image> backgroundImage; | std::shared_ptr<Image> backgroundImage; | ||||
@@ -524,8 +520,12 @@ extern RackScene *gRackScene; | |||||
extern RackWidget *gRackWidget; | extern RackWidget *gRackWidget; | ||||
extern Toolbar *gToolbar; | 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); | json_t *colorToJson(NVGcolor color); | ||||
NVGcolor jsonToColor(json_t *colorJ); | NVGcolor jsonToColor(json_t *colorJ); | ||||
@@ -18,7 +18,10 @@ struct Light { | |||||
void setBrightness(float brightness) { | void setBrightness(float brightness) { | ||||
value = (brightness > 0.f) ? brightness * brightness : 0.f; | 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 { | struct Input { | ||||
@@ -14,6 +14,11 @@ struct Label : Widget { | |||||
void draw(NVGcontext *vg) override; | void draw(NVGcontext *vg) override; | ||||
}; | }; | ||||
struct List : OpaqueWidget { | |||||
void step() override; | |||||
void draw(NVGcontext *vg) override; | |||||
}; | |||||
/** Deletes itself from parent when clicked */ | /** Deletes itself from parent when clicked */ | ||||
struct MenuOverlay : OpaqueWidget { | struct MenuOverlay : OpaqueWidget { | ||||
void step() override; | void step() override; | ||||
@@ -33,7 +38,7 @@ struct Menu : OpaqueWidget { | |||||
box.size = Vec(0, 0); | box.size = Vec(0, 0); | ||||
} | } | ||||
~Menu(); | ~Menu(); | ||||
// Resizes menu and calls addChild() | |||||
/** Deprecated. Just use addChild(child) instead */ | |||||
void pushChild(Widget *child) DEPRECATED { | void pushChild(Widget *child) DEPRECATED { | ||||
addChild(child); | addChild(child); | ||||
} | } | ||||
@@ -44,10 +49,6 @@ struct Menu : OpaqueWidget { | |||||
}; | }; | ||||
struct MenuEntry : OpaqueWidget { | struct MenuEntry : OpaqueWidget { | ||||
MenuEntry() { | |||||
box.size = Vec(0, BND_WIDGET_HEIGHT); | |||||
} | |||||
template <typename T = MenuEntry> | template <typename T = MenuEntry> | ||||
static T *create() { | static T *create() { | ||||
T *o = Widget::create<T>(Vec()); | T *o = Widget::create<T>(Vec()); | ||||
@@ -57,6 +58,9 @@ struct MenuEntry : OpaqueWidget { | |||||
struct MenuLabel : MenuEntry { | struct MenuLabel : MenuEntry { | ||||
std::string text; | std::string text; | ||||
MenuLabel() { | |||||
box.size = Vec(0, BND_WIDGET_HEIGHT); | |||||
} | |||||
void draw(NVGcontext *vg) override; | void draw(NVGcontext *vg) override; | ||||
void step() override; | void step() override; | ||||
@@ -71,6 +75,9 @@ struct MenuLabel : MenuEntry { | |||||
struct MenuItem : MenuEntry { | struct MenuItem : MenuEntry { | ||||
std::string text; | std::string text; | ||||
std::string rightText; | std::string rightText; | ||||
MenuItem() { | |||||
box.size = Vec(0, BND_WIDGET_HEIGHT); | |||||
} | |||||
void draw(NVGcontext *vg) override; | void draw(NVGcontext *vg) override; | ||||
void step() override; | void step() override; | ||||
virtual Menu *createChildMenu() {return NULL;} | virtual Menu *createChildMenu() {return NULL;} | ||||
@@ -89,7 +96,7 @@ struct MenuItem : MenuEntry { | |||||
struct WindowOverlay : OpaqueWidget { | struct WindowOverlay : OpaqueWidget { | ||||
}; | }; | ||||
struct Window : OpaqueWidget { | |||||
struct WindowWidget : OpaqueWidget { | |||||
std::string title; | std::string title; | ||||
void draw(NVGcontext *vg) override; | void draw(NVGcontext *vg) override; | ||||
void onDragMove(EventDragMove &e) override; | void onDragMove(EventDragMove &e) override; | ||||
@@ -139,22 +146,7 @@ struct Slider : OpaqueWidget, QuantityWidget { | |||||
void onMouseDown(EventMouseDown &e) override; | 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 */ | /** Handles a container with ScrollBar */ | ||||
struct ScrollWidget : OpaqueWidget { | struct ScrollWidget : OpaqueWidget { | ||||
Widget *container; | Widget *container; | ||||
@@ -186,7 +178,10 @@ struct TextField : OpaqueWidget { | |||||
void onFocus(EventFocus &e) override; | void onFocus(EventFocus &e) override; | ||||
void onText(EventText &e) override; | void onText(EventText &e) override; | ||||
void onKey(EventKey &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 int getTextPosition(Vec mousePos); | ||||
virtual void onTextChange() {} | virtual void onTextChange() {} | ||||
}; | }; | ||||
@@ -257,6 +257,7 @@ struct FramebufferWidget : virtual Widget { | |||||
void onZoom(EventZoom &e) override; | void onZoom(EventZoom &e) override; | ||||
}; | }; | ||||
/** A Widget representing a float value */ | |||||
struct QuantityWidget : virtual Widget { | struct QuantityWidget : virtual Widget { | ||||
float value = 0.0; | float value = 0.0; | ||||
float minValue = 0.0; | float minValue = 0.0; | ||||
@@ -4,7 +4,7 @@ | |||||
#include <thread> | #include <thread> | ||||
#include <mutex> | #include <mutex> | ||||
#include <condition_variable> | #include <condition_variable> | ||||
#include "core.hpp" | |||||
#include "Core.hpp" | |||||
#include "audio.hpp" | #include "audio.hpp" | ||||
#include "dsp/samplerate.hpp" | #include "dsp/samplerate.hpp" | ||||
#include "dsp/ringbuffer.hpp" | #include "dsp/ringbuffer.hpp" |
@@ -1,4 +1,4 @@ | |||||
#include "core.hpp" | |||||
#include "Core.hpp" | |||||
using namespace rack; | using namespace rack; | ||||
@@ -1,4 +1,4 @@ | |||||
#include "core.hpp" | |||||
#include "Core.hpp" | |||||
void init(rack::Plugin *p) { | void init(rack::Plugin *p) { |
@@ -1,4 +1,4 @@ | |||||
#include "core.hpp" | |||||
#include "Core.hpp" | |||||
#include "midi.hpp" | #include "midi.hpp" | ||||
#include "dsp/filter.hpp" | #include "dsp/filter.hpp" | ||||
@@ -1,4 +1,4 @@ | |||||
#include "core.hpp" | |||||
#include "Core.hpp" | |||||
#include "midi.hpp" | #include "midi.hpp" | ||||
#include "dsp/filter.hpp" | #include "dsp/filter.hpp" | ||||
@@ -1,4 +1,4 @@ | |||||
#include "core.hpp" | |||||
#include "Core.hpp" | |||||
#include "midi.hpp" | #include "midi.hpp" | ||||
#include "dsp/filter.hpp" | #include "dsp/filter.hpp" | ||||
@@ -1,4 +1,4 @@ | |||||
#include "core.hpp" | |||||
#include "Core.hpp" | |||||
using namespace rack; | using namespace rack; | ||||
@@ -1,4 +1,4 @@ | |||||
#include "core.hpp" | |||||
#include "Core.hpp" | |||||
#include "midi.hpp" | #include "midi.hpp" | ||||
@@ -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 |
@@ -94,35 +94,40 @@ void RackScene::onHoverKey(EventHoverKey &e) { | |||||
if (windowIsModPressed() && !windowIsShiftPressed()) { | if (windowIsModPressed() && !windowIsShiftPressed()) { | ||||
gRackWidget->reset(); | gRackWidget->reset(); | ||||
e.consumed = true; | e.consumed = true; | ||||
return; | |||||
} | } | ||||
} break; | } break; | ||||
case GLFW_KEY_Q: { | case GLFW_KEY_Q: { | ||||
if (windowIsModPressed() && !windowIsShiftPressed()) { | if (windowIsModPressed() && !windowIsShiftPressed()) { | ||||
windowClose(); | windowClose(); | ||||
e.consumed = true; | e.consumed = true; | ||||
return; | |||||
} | } | ||||
} break; | } break; | ||||
case GLFW_KEY_O: { | case GLFW_KEY_O: { | ||||
if (windowIsModPressed() && !windowIsShiftPressed()) { | if (windowIsModPressed() && !windowIsShiftPressed()) { | ||||
gRackWidget->openDialog(); | gRackWidget->openDialog(); | ||||
e.consumed = true; | e.consumed = true; | ||||
return; | |||||
} | } | ||||
} break; | } break; | ||||
case GLFW_KEY_S: { | case GLFW_KEY_S: { | ||||
if (windowIsModPressed() && !windowIsShiftPressed()) { | if (windowIsModPressed() && !windowIsShiftPressed()) { | ||||
gRackWidget->saveDialog(); | gRackWidget->saveDialog(); | ||||
e.consumed = true; | e.consumed = true; | ||||
return; | |||||
} | } | ||||
if (windowIsModPressed() && windowIsShiftPressed()) { | if (windowIsModPressed() && windowIsShiftPressed()) { | ||||
gRackWidget->saveAsDialog(); | gRackWidget->saveAsDialog(); | ||||
e.consumed = true; | e.consumed = true; | ||||
return; | |||||
} | } | ||||
} break; | } break; | ||||
case GLFW_KEY_R: { | |||||
if (windowIsModPressed() && !windowIsShiftPressed()) { | |||||
gRackWidget->revert(); | |||||
e.consumed = true; | |||||
} | |||||
} break; | |||||
case GLFW_KEY_ENTER: { | |||||
appModuleBrowserCreate(); | |||||
e.consumed = true; | |||||
} break; | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -141,6 +141,14 @@ void RackWidget::loadPatch(std::string path) { | |||||
fclose(file); | 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() { | json_t *RackWidget::toJson() { | ||||
// root | // root | ||||
json_t *rootJ = json_object(); | json_t *rootJ = json_object(); | ||||
@@ -438,15 +446,7 @@ void RackWidget::onMouseDown(EventMouseDown &e) { | |||||
return; | return; | ||||
if (e.button == 1) { | 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.consumed = true; | ||||
e.target = this; | e.target = this; | ||||
@@ -30,6 +30,12 @@ struct SaveAsItem : MenuItem { | |||||
} | } | ||||
}; | }; | ||||
struct RevertItem : MenuItem { | |||||
void onAction(EventAction &e) override { | |||||
gRackWidget->revert(); | |||||
} | |||||
}; | |||||
struct QuitItem : MenuItem { | struct QuitItem : MenuItem { | ||||
void onAction(EventAction &e) override { | void onAction(EventAction &e) override { | ||||
windowClose(); | windowClose(); | ||||
@@ -42,13 +48,12 @@ struct FileChoice : ChoiceButton { | |||||
menu->box.pos = getAbsoluteOffset(Vec(0, box.size.y)); | menu->box.pos = getAbsoluteOffset(Vec(0, box.size.y)); | ||||
menu->box.size.x = box.size.x; | 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")); | |||||
} | } | ||||
}; | }; | ||||
@@ -13,14 +13,13 @@ Toolbar *gToolbar = NULL; | |||||
RackScene *gRackScene = NULL; | RackScene *gRackScene = NULL; | ||||
void sceneInit() { | |||||
void appInit() { | |||||
gRackScene = new RackScene(); | gRackScene = new RackScene(); | ||||
gScene = gRackScene; | gScene = gRackScene; | ||||
} | } | ||||
void sceneDestroy() { | |||||
delete gScene; | |||||
gScene = NULL; | |||||
void appDestroy() { | |||||
delete gRackScene; | |||||
} | } | ||||
@@ -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 |
@@ -38,11 +38,11 @@ float Light::getBrightness() { | |||||
return sqrtf(fmaxf(0.f, value)); | 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; | float v = (brightness > 0.f) ? brightness * brightness : 0.f; | ||||
if (v < value) { | if (v < value) { | ||||
// Fade out light with lambda = framerate | // Fade out light with lambda = framerate | ||||
value += (v - value) * sampleTime * (60.f * 1.f); | |||||
value += (v - value) * sampleTime * frames * 60.f; | |||||
} | } | ||||
else { | else { | ||||
// Immediately illuminate light | // Immediately illuminate light | ||||
@@ -34,20 +34,22 @@ int main(int argc, char* argv[]) { | |||||
pluginInit(); | pluginInit(); | ||||
engineInit(); | engineInit(); | ||||
windowInit(); | windowInit(); | ||||
sceneInit(); | |||||
appInit(); | |||||
settingsLoad(assetLocal("settings.json")); | 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. | // 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; | bool oldSkipAutosaveOnLaunch = skipAutosaveOnLaunch; | ||||
skipAutosaveOnLaunch = true; | skipAutosaveOnLaunch = true; | ||||
settingsSave(assetLocal("settings.json")); | settingsSave(assetLocal("settings.json")); | ||||
skipAutosaveOnLaunch = false; | 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. | // Do nothing. Empty patch is already loaded. | ||||
} | } | ||||
else { | else { | ||||
gRackWidget->loadPatch(assetLocal("autosave.vcv")); | gRackWidget->loadPatch(assetLocal("autosave.vcv")); | ||||
} | } | ||||
gRackWidget->lastPath = oldLastPath; | |||||
engineStart(); | engineStart(); | ||||
windowRun(); | windowRun(); | ||||
@@ -55,7 +57,7 @@ int main(int argc, char* argv[]) { | |||||
gRackWidget->savePatch(assetLocal("autosave.vcv")); | gRackWidget->savePatch(assetLocal("autosave.vcv")); | ||||
settingsSave(assetLocal("settings.json")); | settingsSave(assetLocal("settings.json")); | ||||
sceneDestroy(); | |||||
appDestroy(); | |||||
windowDestroy(); | windowDestroy(); | ||||
engineDestroy(); | engineDestroy(); | ||||
pluginDestroy(); | pluginDestroy(); | ||||
@@ -64,6 +64,9 @@ static json_t *settingsToJson() { | |||||
json_object_set_new(rootJ, "skipAutosaveOnLaunch", json_true()); | json_object_set_new(rootJ, "skipAutosaveOnLaunch", json_true()); | ||||
} | } | ||||
// moduleBrowser | |||||
json_object_set_new(rootJ, "moduleBrowser", appModuleBrowserToJson()); | |||||
return rootJ; | return rootJ; | ||||
} | } | ||||
@@ -123,9 +126,15 @@ static void settingsFromJson(json_t *rootJ) { | |||||
if (lastPathJ) | if (lastPathJ) | ||||
gRackWidget->lastPath = json_string_value(lastPathJ); | gRackWidget->lastPath = json_string_value(lastPathJ); | ||||
// skipAutosaveOnLaunch | |||||
json_t *skipAutosaveOnLaunchJ = json_object_get(rootJ, "skipAutosaveOnLaunch"); | json_t *skipAutosaveOnLaunchJ = json_object_get(rootJ, "skipAutosaveOnLaunch"); | ||||
if (skipAutosaveOnLaunchJ) | if (skipAutosaveOnLaunchJ) | ||||
skipAutosaveOnLaunch = json_boolean_value(skipAutosaveOnLaunchJ); | skipAutosaveOnLaunch = json_boolean_value(skipAutosaveOnLaunchJ); | ||||
// moduleBrowser | |||||
json_t * moduleBrowserJ = json_object_get(rootJ, "moduleBrowser"); | |||||
if (moduleBrowserJ) | |||||
appModuleBrowserFromJson(moduleBrowserJ); | |||||
} | } | ||||
@@ -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 |
@@ -29,7 +29,7 @@ void Menu::step() { | |||||
for (Widget *child : children) { | for (Widget *child : children) { | ||||
if (!child->visible) | if (!child->visible) | ||||
continue; | continue; | ||||
// Increase height, set position of child | |||||
// Increment height, set position of child | |||||
child->box.pos = Vec(0, box.size.y); | child->box.pos = Vec(0, box.size.y); | ||||
box.size.y += child->box.size.y; | box.size.y += child->box.size.y; | ||||
// Increase width based on maximum width of child | // Increase width based on maximum width of child | ||||
@@ -24,7 +24,7 @@ void MenuItem::draw(NVGcontext *vg) { | |||||
} | } | ||||
void MenuItem::step() { | 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; | const float rightPadding = 10.0; | ||||
// HACK use gVg from the window. | // 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. | // 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; | return; | ||||
EventAction eAction; | 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; | eAction.consumed = true; | ||||
onAction(eAction); | onAction(eAction); | ||||
if (eAction.consumed) { | if (eAction.consumed) { | ||||
@@ -1,4 +1,5 @@ | |||||
#include "ui.hpp" | #include "ui.hpp" | ||||
#include "window.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -23,9 +24,19 @@ void MenuOverlay::onMouseDown(EventMouseDown &e) { | |||||
} | } | ||||
void MenuOverlay::onHoverKey(EventHoverKey &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; | |||||
} | |||||
} | } | ||||
@@ -17,10 +17,10 @@ void RadioButton::onMouseLeave(EventMouseLeave &e) { | |||||
void RadioButton::onDragDrop(EventDragDrop &e) { | void RadioButton::onDragDrop(EventDragDrop &e) { | ||||
if (e.origin == this) { | if (e.origin == this) { | ||||
if (value == 0.0) | |||||
value = 1.0; | |||||
if (value) | |||||
setValue(0.0); | |||||
else | else | ||||
value = 0.0; | |||||
setValue(1.0); | |||||
EventAction eAction; | EventAction eAction; | ||||
onAction(eAction); | onAction(eAction); | ||||
@@ -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 |
@@ -4,6 +4,47 @@ | |||||
namespace rack { | 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() { | ScrollWidget::ScrollWidget() { | ||||
container = new Widget(); | container = new Widget(); | ||||
addChild(container); | addChild(container); | ||||
@@ -21,9 +62,7 @@ ScrollWidget::ScrollWidget() { | |||||
void ScrollWidget::draw(NVGcontext *vg) { | void ScrollWidget::draw(NVGcontext *vg) { | ||||
nvgScissor(vg, 0, 0, box.size.x, box.size.y); | nvgScissor(vg, 0, 0, box.size.x, box.size.y); | ||||
Widget::draw(vg); | Widget::draw(vg); | ||||
nvgResetScissor(vg); | nvgResetScissor(vg); | ||||
} | } | ||||
@@ -32,13 +71,6 @@ void ScrollWidget::step() { | |||||
Vec containerCorner = container->getChildrenBoundingBox().getBottomRight(); | Vec containerCorner = container->getChildrenBoundingBox().getBottomRight(); | ||||
offset = offset.clamp(Rect(Vec(0, 0), containerCorner.minus(box.size))); | 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 | // Update the container's positions from the offset | ||||
container->box.pos = offset.neg().round(); | container->box.pos = offset.neg().round(); | ||||
@@ -47,12 +79,19 @@ void ScrollWidget::step() { | |||||
Vec scrollbarOffset = offset.div(viewportSize.minus(box.size)); | Vec scrollbarOffset = offset.div(viewportSize.minus(box.size)); | ||||
Vec scrollbarSize = box.size.div(viewportSize); | Vec scrollbarSize = box.size.div(viewportSize); | ||||
horizontalScrollBar->offset = scrollbarOffset.x; | |||||
horizontalScrollBar->size = scrollbarSize.x; | |||||
horizontalScrollBar->visible = (0.0 < scrollbarSize.x && scrollbarSize.x < 1.0); | 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; | verticalScrollBar->offset = scrollbarOffset.y; | ||||
horizontalScrollBar->size = scrollbarSize.x; | |||||
verticalScrollBar->size = scrollbarSize.y; | 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(); | Widget::step(); | ||||
} | } | ||||
@@ -155,15 +155,20 @@ void TextField::onKey(EventKey &e) { | |||||
e.consumed = true; | e.consumed = true; | ||||
} | } | ||||
void TextField::insertText(std::string newText) { | |||||
void TextField::insertText(std::string text) { | |||||
if (begin < end) | 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; | end = begin; | ||||
onTextChange(); | onTextChange(); | ||||
} | } | ||||
void TextField::setText(std::string text) { | |||||
this->text = text; | |||||
onTextChange(); | |||||
} | |||||
int TextField::getTextPosition(Vec mousePos) { | 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); | return bndTextFieldTextPosition(gVg, 0.0, 0.0, box.size.x, box.size.y, -1, text.c_str(), mousePos.x, mousePos.y); | ||||
} | } | ||||
@@ -4,12 +4,12 @@ | |||||
namespace rack { | 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); | bndNodeBackground(vg, 0.0, 0.0, box.size.x, box.size.y, BND_DEFAULT, -1, title.c_str(), bndGetTheme()->backgroundColor); | ||||
Widget::draw(vg); | Widget::draw(vg); | ||||
} | } | ||||
void Window::onDragMove(EventDragMove &e) { | |||||
void WindowWidget::onDragMove(EventDragMove &e) { | |||||
box.pos = box.pos.plus(e.mouseRel); | box.pos = box.pos.plus(e.mouseRel); | ||||
} | } | ||||
@@ -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 |