| @@ -1 +1 @@ | |||
| Subproject commit ac99b8dd4d36ac314dad6d700701e39484c60e06 | |||
| Subproject commit 7c6038857b1bb6299c2d20ff9f31248f2d53ba39 | |||
| @@ -153,6 +153,13 @@ 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; | |||
| @@ -66,10 +66,10 @@ inline float nearf(float a, float b, float epsilon = 1e-6) { | |||
| } | |||
| /** Limits a value between a minimum and maximum | |||
| If min > max, the limits are switched | |||
| If min > max, returns min | |||
| */ | |||
| inline float clampf(float x, float min, float max) { | |||
| return fmaxf(fminf(x, fmaxf(min, max)), fminf(min, max)); | |||
| return fmaxf(fminf(x, max), min); | |||
| } | |||
| /** If the magnitude of x if less than eps, return 0 */ | |||
| @@ -72,6 +72,8 @@ float randomNormal(); | |||
| /** Converts a printf format string and optional arguments into a std::string */ | |||
| std::string stringf(const char *format, ...); | |||
| std::string tolower(std::string s); | |||
| std::string toupper(std::string s); | |||
| /** Truncates and adds "..." to a string, not exceeding `len` characters */ | |||
| std::string ellipsize(std::string s, size_t len); | |||
| @@ -292,10 +292,10 @@ struct Label : Widget { | |||
| void draw(NVGcontext *vg) override; | |||
| }; | |||
| // Deletes itself from parent when clicked | |||
| /** Deletes itself from parent when clicked */ | |||
| struct MenuOverlay : OpaqueWidget { | |||
| void step() override; | |||
| void onDragDrop(EventDragDrop &e) override; | |||
| void onScroll(EventScroll &e) override; | |||
| void onHoverKey(EventHoverKey &e) override; | |||
| }; | |||
| @@ -323,24 +323,34 @@ struct Menu : OpaqueWidget { | |||
| struct MenuEntry : OpaqueWidget { | |||
| std::string text; | |||
| std::string rightText; | |||
| MenuEntry() { | |||
| box.size = Vec(0, BND_WIDGET_HEIGHT); | |||
| } | |||
| void step() override; | |||
| }; | |||
| struct MenuLabel : MenuEntry { | |||
| void draw(NVGcontext *vg) override; | |||
| void step() override; | |||
| }; | |||
| struct MenuItem : MenuEntry { | |||
| std::string rightText; | |||
| void draw(NVGcontext *vg) override; | |||
| void step() override; | |||
| virtual Menu *createChildMenu() {return NULL;} | |||
| void onMouseEnter(EventMouseEnter &e) override; | |||
| void onDragDrop(EventDragDrop &e) override; | |||
| }; | |||
| struct WindowOverlay : OpaqueWidget { | |||
| }; | |||
| struct Window : OpaqueWidget { | |||
| std::string title; | |||
| void draw(NVGcontext *vg) override; | |||
| void onDragMove(EventDragMove &e) override; | |||
| }; | |||
| struct Button : OpaqueWidget { | |||
| std::string text; | |||
| BNDwidgetState state = BND_DEFAULT; | |||
| @@ -389,6 +399,8 @@ struct Slider : OpaqueWidget, QuantityWidget { | |||
| 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); | |||
| @@ -407,7 +419,9 @@ struct ScrollWidget : OpaqueWidget { | |||
| Vec offset; | |||
| ScrollWidget(); | |||
| void draw(NVGcontext *vg) override; | |||
| void step() override; | |||
| void onMouseMove(EventMouseMove &e) override; | |||
| void onScroll(EventScroll &e) override; | |||
| }; | |||
| @@ -0,0 +1,295 @@ | |||
| #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) { | |||
| clearChildren(); | |||
| if (sModel) { | |||
| // Tag list | |||
| if (!sModel->tags.empty()) { | |||
| for (ModelTag tag : sModel->tags) { | |||
| addChild(construct<MenuLabel>(&MenuEntry::text, gTagNames[tag])); | |||
| } | |||
| addChild(construct<MenuEntry>()); | |||
| } | |||
| // Plugin name | |||
| std::string pluginName = sModel->plugin->slug; | |||
| if (!sModel->plugin->version.empty()) { | |||
| pluginName += " v"; | |||
| pluginName += sModel->plugin->version; | |||
| } | |||
| addChild(construct<MenuLabel>(&MenuEntry::text, pluginName)); | |||
| // Plugin metadata | |||
| if (!sModel->plugin->website.empty()) { | |||
| addChild(construct<UrlItem>(&MenuEntry::text, "Website", &UrlItem::url, sModel->plugin->path)); | |||
| } | |||
| if (!sModel->plugin->manual.empty()) { | |||
| addChild(construct<UrlItem>(&MenuEntry::text, "Manual", &UrlItem::url, sModel->plugin->manual)); | |||
| } | |||
| if (!sModel->plugin->path.empty()) { | |||
| addChild(construct<UrlItem>(&MenuEntry::text, "Browse directory", &UrlItem::url, sModel->plugin->path)); | |||
| } | |||
| } | |||
| model = sModel; | |||
| } | |||
| 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 = tolower(str); | |||
| search = tolower(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) { | |||
| clearChildren(); | |||
| // Add models for the selected manufacturer | |||
| for (Plugin *plugin : gPlugins) { | |||
| for (Model *model : plugin->models) { | |||
| if (model->manufacturer == sManufacturer) { | |||
| addChild(construct<ModelItem>(&MenuEntry::text, model->name, &ModelItem::model, model)); | |||
| } | |||
| } | |||
| } | |||
| manufacturer = sManufacturer; | |||
| filter = ""; | |||
| } | |||
| 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) { | |||
| ModelItem *item = dynamic_cast<ModelItem*>(child); | |||
| if (!item) | |||
| continue; | |||
| if (isModelMatch(item->model, sFilter)) { | |||
| item->visible = true; | |||
| } | |||
| } | |||
| filter = sFilter; | |||
| } | |||
| ListMenu::step(); | |||
| } | |||
| }; | |||
| struct ManufacturerItem : MenuItem { | |||
| Model *model; | |||
| void onAction(EventAction &e) override { | |||
| e.consumed = false; | |||
| } | |||
| void onMouseEnter(EventMouseEnter &e) override { | |||
| sManufacturer = text; | |||
| MenuItem::onMouseEnter(e); | |||
| } | |||
| }; | |||
| struct ManufacturerMenu : ListMenu { | |||
| std::string filter; | |||
| ManufacturerMenu() { | |||
| // 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>(&MenuEntry::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); | |||
| assert(item); | |||
| 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); | |||
| // NVGcolor c = bndTransparent(nvgRGB(0, 0, 0)); | |||
| NVGcolor c = bndGetTheme()->nodeTheme.nodeBackdropColor; | |||
| printf("%f %f %f %f\n", c.r, c.g, c.b, c.a); | |||
| } | |||
| void AddModuleWindow::step() { | |||
| Widget::step(); | |||
| } | |||
| } // namespace rack | |||
| @@ -6,8 +6,6 @@ | |||
| #include "asset.hpp" | |||
| #include <map> | |||
| #include <algorithm> | |||
| #include <thread> | |||
| #include <set> | |||
| #include "../ext/osdialog/osdialog.h" | |||
| @@ -375,111 +373,6 @@ void RackWidget::draw(NVGcontext *vg) { | |||
| Widget::draw(vg); | |||
| } | |||
| struct UrlItem : MenuItem { | |||
| std::string url; | |||
| void onAction(EventAction &e) override { | |||
| std::thread t(openBrowser, url); | |||
| t.detach(); | |||
| } | |||
| }; | |||
| struct AddModuleMenuItem : MenuItem { | |||
| Model *model; | |||
| Vec modulePos; | |||
| 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; | |||
| box.pos = modulePos.minus(box.getCenter()); | |||
| gRackWidget->requestModuleBoxNearest(moduleWidget, box); | |||
| } | |||
| Menu *createChildMenu() override { | |||
| Menu *menu = new Menu(); | |||
| // Tag list | |||
| if (!model->tags.empty()) { | |||
| for (ModelTag tag : model->tags) { | |||
| menu->addChild(construct<MenuLabel>(&MenuEntry::text, gTagNames[tag])); | |||
| } | |||
| menu->addChild(construct<MenuEntry>()); | |||
| } | |||
| // Plugin name | |||
| std::string pluginName = model->plugin->slug; | |||
| if (!model->plugin->version.empty()) { | |||
| pluginName += " v"; | |||
| pluginName += model->plugin->version; | |||
| } | |||
| menu->addChild(construct<MenuLabel>(&MenuEntry::text, pluginName)); | |||
| // Plugin metadata | |||
| if (!model->plugin->website.empty()) { | |||
| menu->addChild(construct<UrlItem>(&MenuEntry::text, "Website", &UrlItem::url, model->plugin->path)); | |||
| } | |||
| if (!model->plugin->manual.empty()) { | |||
| menu->addChild(construct<UrlItem>(&MenuEntry::text, "Manual", &UrlItem::url, model->plugin->manual)); | |||
| } | |||
| if (!model->plugin->path.empty()) { | |||
| menu->addChild(construct<UrlItem>(&MenuEntry::text, "Browse directory", &UrlItem::url, model->plugin->path)); | |||
| } | |||
| return menu; | |||
| } | |||
| }; | |||
| struct AddManufacturerMenuItem : MenuItem { | |||
| std::string manufacturer; | |||
| Vec modulePos; | |||
| void onAction(EventAction &e) override { | |||
| e.consumed = false; | |||
| } | |||
| Menu *createChildMenu() override { | |||
| Menu *menu = new Menu(); | |||
| // Collect models which have this manufacturer name | |||
| for (Plugin *plugin : gPlugins) { | |||
| for (Model *model : plugin->models) { | |||
| if (model->manufacturer == manufacturer) { | |||
| AddModuleMenuItem *item = new AddModuleMenuItem(); | |||
| item->text = model->name; | |||
| // item->rightText = model->plugin->slug; | |||
| // if (!model->plugin->version.empty()) | |||
| // item->rightText += " v" + model->plugin->version; | |||
| item->model = model; | |||
| item->modulePos = modulePos; | |||
| menu->addChild(item); | |||
| } | |||
| } | |||
| } | |||
| return menu; | |||
| } | |||
| }; | |||
| struct SearchModuleField : TextField { | |||
| void onTextChange() override { | |||
| Menu *parentMenu = getAncestorOfType<Menu>(); | |||
| assert(parentMenu); | |||
| for (Widget *w : parentMenu->children) { | |||
| AddManufacturerMenuItem *a = dynamic_cast<AddManufacturerMenuItem*>(w); | |||
| if (!a) | |||
| continue; | |||
| if (a->manufacturer == text) { | |||
| a->visible = true; | |||
| } | |||
| else { | |||
| a->visible = false; | |||
| } | |||
| } | |||
| } | |||
| }; | |||
| void RackWidget::onMouseMove(EventMouseMove &e) { | |||
| OpaqueWidget::onMouseMove(e); | |||
| lastMousePos = e.pos; | |||
| @@ -491,29 +384,15 @@ void RackWidget::onMouseDown(EventMouseDown &e) { | |||
| return; | |||
| if (e.button == 1) { | |||
| Menu *menu = gScene->createMenu(); | |||
| // TextField *searchField = construct<SearchModuleField>(); | |||
| // searchField->box.size.x = 100.0; | |||
| // menu->addChild(searchField); | |||
| // // Focus search field | |||
| // gFocusedWidget = searchField; | |||
| // 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) { | |||
| AddManufacturerMenuItem *item = new AddManufacturerMenuItem(); | |||
| item->text = manufacturer; | |||
| item->manufacturer = manufacturer; | |||
| item->modulePos = e.pos; | |||
| menu->addChild(item); | |||
| } | |||
| 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); | |||
| } | |||
| e.consumed = true; | |||
| e.target = this; | |||
| @@ -364,6 +364,12 @@ void guiInit() { | |||
| gGuiFont = Font::load(assetGlobal("res/DejaVuSans.ttf")); | |||
| bndSetFont(gGuiFont->handle); | |||
| // bndSetIconImage(loadImage(assetGlobal("res/icons.png"))); | |||
| // Blendish style | |||
| BNDtheme theme; | |||
| theme = *bndGetTheme(); | |||
| theme.nodeTheme.nodeBackdropColor = theme.menuTheme.innerColor; | |||
| bndSetTheme(theme); | |||
| } | |||
| void guiDestroy() { | |||
| @@ -3,6 +3,7 @@ | |||
| #include <stdarg.h> | |||
| #include <string.h> | |||
| #include <random> | |||
| #include <algorithm> | |||
| #include <libgen.h> // for dirname and basename | |||
| #include <sys/time.h> | |||
| @@ -97,6 +98,16 @@ std::string stringf(const char *format, ...) { | |||
| return s; | |||
| } | |||
| std::string tolower(std::string s) { | |||
| std::transform(s.begin(), s.end(), s.begin(), ::tolower); | |||
| return s; | |||
| } | |||
| std::string toupper(std::string s) { | |||
| std::transform(s.begin(), s.end(), s.begin(), ::toupper); | |||
| return s; | |||
| } | |||
| std::string ellipsize(std::string s, size_t len) { | |||
| if (s.size() <= len) | |||
| return s; | |||
| @@ -42,10 +42,6 @@ void Menu::step() { | |||
| for (Widget *child : children) { | |||
| child->box.size.x = box.size.x; | |||
| } | |||
| // Try to fit into the parent's box | |||
| if (parent) | |||
| box = box.nudge(Rect(Vec(0, 0), parent->box.size)); | |||
| } | |||
| void Menu::draw(NVGcontext *vg) { | |||
| @@ -1,18 +0,0 @@ | |||
| #include "widgets.hpp" | |||
| #include "gui.hpp" | |||
| namespace rack { | |||
| void MenuEntry::step() { | |||
| // Add 10 more pixels because Retina measurements are sometimes too small | |||
| const float rightPadding = 10.0; | |||
| // HACK use gVg from the gui. | |||
| // All this does is inspect the font, so it shouldn't modify gVg and should work when called from a FramebufferWidget for example. | |||
| box.size.x = bndLabelWidth(gVg, -1, text.c_str()) + bndLabelWidth(gVg, -1, rightText.c_str()) + rightPadding; | |||
| Widget::step(); | |||
| } | |||
| } // namespace rack | |||
| @@ -1,4 +1,5 @@ | |||
| #include "widgets.hpp" | |||
| #include "gui.hpp" | |||
| namespace rack { | |||
| @@ -22,6 +23,15 @@ void MenuItem::draw(NVGcontext *vg) { | |||
| bndIconLabelValue(vg, x, 0.0, box.size.x, box.size.y, -1, rightColor, BND_LEFT, BND_LABEL_FONT_SIZE, rightText.c_str(), NULL); | |||
| } | |||
| void MenuItem::step() { | |||
| // Add 10 more pixels because Retina measurements are sometimes too small | |||
| const float rightPadding = 10.0; | |||
| // HACK use gVg from the gui. | |||
| // All this does is inspect the font, so it shouldn't modify gVg and should work when called from a FramebufferWidget for example. | |||
| box.size.x = bndLabelWidth(gVg, -1, text.c_str()) + bndLabelWidth(gVg, -1, rightText.c_str()) + rightPadding; | |||
| Widget::step(); | |||
| } | |||
| void MenuItem::onMouseEnter(EventMouseEnter &e) { | |||
| Menu *parentMenu = dynamic_cast<Menu*>(parent); | |||
| if (!parentMenu) | |||
| @@ -1,4 +1,5 @@ | |||
| #include "widgets.hpp" | |||
| #include "gui.hpp" | |||
| namespace rack { | |||
| @@ -8,5 +9,13 @@ void MenuLabel::draw(NVGcontext *vg) { | |||
| bndMenuLabel(vg, 0.0, 0.0, box.size.x, box.size.y, -1, text.c_str()); | |||
| } | |||
| void MenuLabel::step() { | |||
| // Add 10 more pixels because Retina measurements are sometimes too small | |||
| const float rightPadding = 10.0; | |||
| // HACK use gVg from the gui. | |||
| box.size.x = bndLabelWidth(gVg, -1, text.c_str()) + rightPadding; | |||
| Widget::step(); | |||
| } | |||
| } // namespace rack | |||
| @@ -3,6 +3,16 @@ | |||
| namespace rack { | |||
| void MenuOverlay::step() { | |||
| Widget::step(); | |||
| // Fit all children in the box | |||
| for (Widget *child : children) { | |||
| child->box = child->box.nudge(Rect(Vec(0, 0), parent->box.size)); | |||
| } | |||
| } | |||
| void MenuOverlay::onDragDrop(EventDragDrop &e) { | |||
| if (e.origin == this) { | |||
| // deletes `this` | |||
| @@ -10,11 +20,6 @@ void MenuOverlay::onDragDrop(EventDragDrop &e) { | |||
| } | |||
| } | |||
| void MenuOverlay::onScroll(EventScroll &e) { | |||
| // Don't recurse children, consume the event | |||
| e.consumed = true; | |||
| } | |||
| void MenuOverlay::onHoverKey(EventHoverKey &e) { | |||
| // Recurse children but consume the event | |||
| Widget::onHoverKey(e); | |||
| @@ -31,6 +31,7 @@ Menu *Scene::createMenu() { | |||
| void Scene::step() { | |||
| if (overlay) { | |||
| overlay->box.pos = Vec(0, 0); | |||
| overlay->box.size = box.size; | |||
| } | |||
| @@ -6,16 +6,6 @@ namespace rack { | |||
| void ScrollBar::draw(NVGcontext *vg) { | |||
| ScrollWidget *scrollWidget = dynamic_cast<ScrollWidget*>(parent); | |||
| assert(scrollWidget); | |||
| Vec viewportCorner = scrollWidget->container->getChildrenBoundingBox().getBottomRight(); | |||
| float viewportSize = (orientation == HORIZONTAL) ? viewportCorner.x : viewportCorner.y; | |||
| float containerSize = (orientation == HORIZONTAL) ? scrollWidget->box.size.x : scrollWidget->box.size.y; | |||
| float viewportOffset = (orientation == HORIZONTAL) ? scrollWidget->offset.x : scrollWidget->offset.y; | |||
| float offset = viewportOffset / (viewportSize - containerSize); | |||
| float size = containerSize / viewportSize; | |||
| size = clampf(size, 0.0, 1.0); | |||
| bndScrollBar(vg, 0.0, 0.0, box.size.x, box.size.y, state, offset, size); | |||
| } | |||
| @@ -10,13 +10,23 @@ ScrollWidget::ScrollWidget() { | |||
| horizontalScrollBar = new ScrollBar(); | |||
| horizontalScrollBar->orientation = ScrollBar::HORIZONTAL; | |||
| horizontalScrollBar->visible = false; | |||
| addChild(horizontalScrollBar); | |||
| verticalScrollBar = new ScrollBar(); | |||
| verticalScrollBar->orientation = ScrollBar::VERTICAL; | |||
| verticalScrollBar->visible = false; | |||
| addChild(verticalScrollBar); | |||
| } | |||
| void ScrollWidget::draw(NVGcontext *vg) { | |||
| nvgScissor(vg, 0, 0, box.size.x, box.size.y); | |||
| Widget::draw(vg); | |||
| nvgResetScissor(vg); | |||
| } | |||
| void ScrollWidget::step() { | |||
| // Clamp scroll offset | |||
| Vec containerCorner = container->getChildrenBoundingBox().getBottomRight(); | |||
| @@ -32,6 +42,22 @@ void ScrollWidget::step() { | |||
| // Update the container's positions from the offset | |||
| container->box.pos = offset.neg().round(); | |||
| // Update scrollbar offsets and sizes | |||
| Vec viewportSize = container->getChildrenBoundingBox().getBottomRight(); | |||
| 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->offset = scrollbarOffset.y; | |||
| verticalScrollBar->size = scrollbarSize.y; | |||
| verticalScrollBar->visible = (0.0 < scrollbarSize.y && scrollbarSize.y < 1.0); | |||
| Widget::step(); | |||
| } | |||
| void ScrollWidget::onMouseMove(EventMouseMove &e) { | |||
| // Scroll with arrow keys | |||
| if (!gFocusedWidget) { | |||
| float arrowSpeed = 30.0; | |||
| @@ -55,7 +81,8 @@ void ScrollWidget::step() { | |||
| offset.y += arrowSpeed; | |||
| } | |||
| } | |||
| Widget::step(); | |||
| Widget::onMouseMove(e); | |||
| } | |||
| void ScrollWidget::onScroll(EventScroll &e) { | |||
| @@ -0,0 +1,17 @@ | |||
| #include "widgets.hpp" | |||
| namespace rack { | |||
| void Window::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) { | |||
| box.pos = box.pos.plus(e.mouseRel); | |||
| } | |||
| } // namespace rack | |||