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