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