| @@ -1 +1 @@ | |||||
| Subproject commit ac99b8dd4d36ac314dad6d700701e39484c60e06 | |||||
| Subproject commit 7c6038857b1bb6299c2d20ff9f31248f2d53ba39 | |||||
| @@ -153,6 +153,13 @@ 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; | ||||
| @@ -66,10 +66,10 @@ inline float nearf(float a, float b, float epsilon = 1e-6) { | |||||
| } | } | ||||
| /** Limits a value between a minimum and maximum | /** 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) { | 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 */ | /** 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 */ | /** Converts a printf format string and optional arguments into a std::string */ | ||||
| std::string stringf(const char *format, ...); | 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 */ | /** Truncates and adds "..." to a string, not exceeding `len` characters */ | ||||
| std::string ellipsize(std::string s, size_t len); | std::string ellipsize(std::string s, size_t len); | ||||
| @@ -292,10 +292,10 @@ struct Label : Widget { | |||||
| void draw(NVGcontext *vg) 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 onDragDrop(EventDragDrop &e) override; | void onDragDrop(EventDragDrop &e) override; | ||||
| void onScroll(EventScroll &e) override; | |||||
| void onHoverKey(EventHoverKey &e) override; | void onHoverKey(EventHoverKey &e) override; | ||||
| }; | }; | ||||
| @@ -323,24 +323,34 @@ struct Menu : OpaqueWidget { | |||||
| struct MenuEntry : OpaqueWidget { | struct MenuEntry : OpaqueWidget { | ||||
| std::string text; | std::string text; | ||||
| std::string rightText; | |||||
| MenuEntry() { | MenuEntry() { | ||||
| box.size = Vec(0, BND_WIDGET_HEIGHT); | box.size = Vec(0, BND_WIDGET_HEIGHT); | ||||
| } | } | ||||
| void step() override; | |||||
| }; | }; | ||||
| struct MenuLabel : MenuEntry { | struct MenuLabel : MenuEntry { | ||||
| void draw(NVGcontext *vg) override; | void draw(NVGcontext *vg) override; | ||||
| void step() override; | |||||
| }; | }; | ||||
| struct MenuItem : MenuEntry { | struct MenuItem : MenuEntry { | ||||
| std::string rightText; | |||||
| void draw(NVGcontext *vg) override; | void draw(NVGcontext *vg) override; | ||||
| void step() override; | |||||
| virtual Menu *createChildMenu() {return NULL;} | virtual Menu *createChildMenu() {return NULL;} | ||||
| void onMouseEnter(EventMouseEnter &e) override; | void onMouseEnter(EventMouseEnter &e) override; | ||||
| void onDragDrop(EventDragDrop &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 { | struct Button : OpaqueWidget { | ||||
| std::string text; | std::string text; | ||||
| BNDwidgetState state = BND_DEFAULT; | BNDwidgetState state = BND_DEFAULT; | ||||
| @@ -389,6 +399,8 @@ struct Slider : OpaqueWidget, QuantityWidget { | |||||
| struct ScrollBar : OpaqueWidget { | struct ScrollBar : OpaqueWidget { | ||||
| enum { VERTICAL, HORIZONTAL } orientation; | enum { VERTICAL, HORIZONTAL } orientation; | ||||
| BNDwidgetState state = BND_DEFAULT; | BNDwidgetState state = BND_DEFAULT; | ||||
| float offset = 0.0; | |||||
| float size = 0.0; | |||||
| ScrollBar() { | ScrollBar() { | ||||
| box.size = Vec(BND_SCROLLBAR_WIDTH, BND_SCROLLBAR_HEIGHT); | box.size = Vec(BND_SCROLLBAR_WIDTH, BND_SCROLLBAR_HEIGHT); | ||||
| @@ -407,7 +419,9 @@ struct ScrollWidget : OpaqueWidget { | |||||
| Vec offset; | Vec offset; | ||||
| ScrollWidget(); | ScrollWidget(); | ||||
| void draw(NVGcontext *vg) override; | |||||
| void step() override; | void step() override; | ||||
| void onMouseMove(EventMouseMove &e) override; | |||||
| void onScroll(EventScroll &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 "asset.hpp" | ||||
| #include <map> | #include <map> | ||||
| #include <algorithm> | #include <algorithm> | ||||
| #include <thread> | |||||
| #include <set> | |||||
| #include "../ext/osdialog/osdialog.h" | #include "../ext/osdialog/osdialog.h" | ||||
| @@ -375,111 +373,6 @@ void RackWidget::draw(NVGcontext *vg) { | |||||
| Widget::draw(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) { | void RackWidget::onMouseMove(EventMouseMove &e) { | ||||
| OpaqueWidget::onMouseMove(e); | OpaqueWidget::onMouseMove(e); | ||||
| lastMousePos = e.pos; | lastMousePos = e.pos; | ||||
| @@ -491,29 +384,15 @@ void RackWidget::onMouseDown(EventMouseDown &e) { | |||||
| return; | return; | ||||
| if (e.button == 1) { | 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.consumed = true; | ||||
| e.target = this; | e.target = this; | ||||
| @@ -364,6 +364,12 @@ void guiInit() { | |||||
| gGuiFont = Font::load(assetGlobal("res/DejaVuSans.ttf")); | gGuiFont = Font::load(assetGlobal("res/DejaVuSans.ttf")); | ||||
| bndSetFont(gGuiFont->handle); | bndSetFont(gGuiFont->handle); | ||||
| // bndSetIconImage(loadImage(assetGlobal("res/icons.png"))); | // bndSetIconImage(loadImage(assetGlobal("res/icons.png"))); | ||||
| // Blendish style | |||||
| BNDtheme theme; | |||||
| theme = *bndGetTheme(); | |||||
| theme.nodeTheme.nodeBackdropColor = theme.menuTheme.innerColor; | |||||
| bndSetTheme(theme); | |||||
| } | } | ||||
| void guiDestroy() { | void guiDestroy() { | ||||
| @@ -3,6 +3,7 @@ | |||||
| #include <stdarg.h> | #include <stdarg.h> | ||||
| #include <string.h> | #include <string.h> | ||||
| #include <random> | #include <random> | ||||
| #include <algorithm> | |||||
| #include <libgen.h> // for dirname and basename | #include <libgen.h> // for dirname and basename | ||||
| #include <sys/time.h> | #include <sys/time.h> | ||||
| @@ -97,6 +98,16 @@ std::string stringf(const char *format, ...) { | |||||
| return s; | 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) { | std::string ellipsize(std::string s, size_t len) { | ||||
| if (s.size() <= len) | if (s.size() <= len) | ||||
| return s; | return s; | ||||
| @@ -42,10 +42,6 @@ void Menu::step() { | |||||
| for (Widget *child : children) { | for (Widget *child : children) { | ||||
| child->box.size.x = box.size.x; | 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) { | 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 "widgets.hpp" | ||||
| #include "gui.hpp" | |||||
| namespace rack { | 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); | 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) { | void MenuItem::onMouseEnter(EventMouseEnter &e) { | ||||
| Menu *parentMenu = dynamic_cast<Menu*>(parent); | Menu *parentMenu = dynamic_cast<Menu*>(parent); | ||||
| if (!parentMenu) | if (!parentMenu) | ||||
| @@ -1,4 +1,5 @@ | |||||
| #include "widgets.hpp" | #include "widgets.hpp" | ||||
| #include "gui.hpp" | |||||
| namespace rack { | 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()); | 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 | } // namespace rack | ||||
| @@ -3,6 +3,16 @@ | |||||
| namespace rack { | 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) { | void MenuOverlay::onDragDrop(EventDragDrop &e) { | ||||
| if (e.origin == this) { | if (e.origin == this) { | ||||
| // deletes `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) { | void MenuOverlay::onHoverKey(EventHoverKey &e) { | ||||
| // Recurse children but consume the event | // Recurse children but consume the event | ||||
| Widget::onHoverKey(e); | Widget::onHoverKey(e); | ||||
| @@ -31,6 +31,7 @@ Menu *Scene::createMenu() { | |||||
| void Scene::step() { | void Scene::step() { | ||||
| if (overlay) { | if (overlay) { | ||||
| overlay->box.pos = Vec(0, 0); | |||||
| overlay->box.size = box.size; | overlay->box.size = box.size; | ||||
| } | } | ||||
| @@ -6,16 +6,6 @@ namespace rack { | |||||
| void ScrollBar::draw(NVGcontext *vg) { | 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); | 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 = new ScrollBar(); | ||||
| horizontalScrollBar->orientation = ScrollBar::HORIZONTAL; | horizontalScrollBar->orientation = ScrollBar::HORIZONTAL; | ||||
| horizontalScrollBar->visible = false; | |||||
| addChild(horizontalScrollBar); | addChild(horizontalScrollBar); | ||||
| verticalScrollBar = new ScrollBar(); | verticalScrollBar = new ScrollBar(); | ||||
| verticalScrollBar->orientation = ScrollBar::VERTICAL; | verticalScrollBar->orientation = ScrollBar::VERTICAL; | ||||
| verticalScrollBar->visible = false; | |||||
| addChild(verticalScrollBar); | 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() { | void ScrollWidget::step() { | ||||
| // Clamp scroll offset | // Clamp scroll offset | ||||
| Vec containerCorner = container->getChildrenBoundingBox().getBottomRight(); | Vec containerCorner = container->getChildrenBoundingBox().getBottomRight(); | ||||
| @@ -32,6 +42,22 @@ void ScrollWidget::step() { | |||||
| // 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(); | ||||
| // 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 | // Scroll with arrow keys | ||||
| if (!gFocusedWidget) { | if (!gFocusedWidget) { | ||||
| float arrowSpeed = 30.0; | float arrowSpeed = 30.0; | ||||
| @@ -55,7 +81,8 @@ void ScrollWidget::step() { | |||||
| offset.y += arrowSpeed; | offset.y += arrowSpeed; | ||||
| } | } | ||||
| } | } | ||||
| Widget::step(); | |||||
| Widget::onMouseMove(e); | |||||
| } | } | ||||
| void ScrollWidget::onScroll(EventScroll &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 | |||||