@@ -7,9 +7,6 @@ | |||||
namespace rack { | namespace rack { | ||||
static const float KNOB_SENSITIVITY = 0.0015f; | |||||
/** Implements vertical dragging behavior for ParamWidgets */ | /** Implements vertical dragging behavior for ParamWidgets */ | ||||
struct Knob : ParamWidget { | struct Knob : ParamWidget { | ||||
/** Multiplier for mouse movement to adjust knob value */ | /** Multiplier for mouse movement to adjust knob value */ | ||||
@@ -1,11 +1,23 @@ | |||||
#pragma once | #pragma once | ||||
#include "app/common.hpp" | #include "app/common.hpp" | ||||
#include "ui/ScrollWidget.hpp" | |||||
#include "ui/SequentialLayout.hpp" | |||||
namespace rack { | namespace rack { | ||||
void moduleBrowserCreate(); | |||||
struct ModuleBrowser : OpaqueWidget { | |||||
ScrollWidget *moduleScroll; | |||||
SequentialLayout *moduleLayout; | |||||
ModuleBrowser(); | |||||
void step() override; | |||||
void draw(NVGcontext *vg) override; | |||||
void onHoverKey(const event::HoverKey &e) override; | |||||
}; | |||||
json_t *moduleBrowserToJson(); | json_t *moduleBrowserToJson(); | ||||
void moduleBrowserFromJson(json_t *rootJ); | void moduleBrowserFromJson(json_t *rootJ); | ||||
@@ -5,6 +5,7 @@ | |||||
#include "ui/ScrollWidget.hpp" | #include "ui/ScrollWidget.hpp" | ||||
#include "app/RackWidget.hpp" | #include "app/RackWidget.hpp" | ||||
#include "app/Toolbar.hpp" | #include "app/Toolbar.hpp" | ||||
#include "app/ModuleBrowser.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -16,6 +17,7 @@ struct Scene : OpaqueWidget { | |||||
ZoomWidget *zoomWidget; | ZoomWidget *zoomWidget; | ||||
RackWidget *rackWidget; | RackWidget *rackWidget; | ||||
Toolbar *toolbar; | Toolbar *toolbar; | ||||
ModuleBrowser *moduleBrowser; | |||||
// Version checking | // Version checking | ||||
bool devMode = false; | bool devMode = false; | ||||
@@ -221,10 +221,10 @@ struct Zoom : Event { | |||||
struct State { | struct State { | ||||
Widget *rootWidget = NULL; | |||||
/** State widgets | /** State widgets | ||||
Don't set these directly unless you know what you're doing. Use the set*() methods instead. | Don't set these directly unless you know what you're doing. Use the set*() methods instead. | ||||
*/ | */ | ||||
Widget *rootWidget = NULL; | |||||
Widget *hoveredWidget = NULL; | Widget *hoveredWidget = NULL; | ||||
Widget *draggedWidget = NULL; | Widget *draggedWidget = NULL; | ||||
Widget *dragHoveredWidget = NULL; | Widget *dragHoveredWidget = NULL; | ||||
@@ -7,14 +7,15 @@ namespace rack { | |||||
struct Label : virtual Widget { | struct Label : virtual Widget { | ||||
std::string text; | |||||
float fontSize; | |||||
NVGcolor color; | |||||
enum Alignment { | enum Alignment { | ||||
LEFT_ALIGNMENT, | LEFT_ALIGNMENT, | ||||
CENTER_ALIGNMENT, | CENTER_ALIGNMENT, | ||||
RIGHT_ALIGNMENT, | RIGHT_ALIGNMENT, | ||||
}; | }; | ||||
std::string text; | |||||
float fontSize; | |||||
NVGcolor color; | |||||
Alignment alignment = LEFT_ALIGNMENT; | Alignment alignment = LEFT_ALIGNMENT; | ||||
Label() { | Label() { | ||||
@@ -0,0 +1,28 @@ | |||||
#pragma once | |||||
#include "widgets/OpaqueWidget.hpp" | |||||
#include "ui/common.hpp" | |||||
namespace rack { | |||||
/** Parent must be a ScrollWidget */ | |||||
struct ScrollBar : OpaqueWidget { | |||||
enum Orientation { | |||||
VERTICAL, | |||||
HORIZONTAL | |||||
}; | |||||
Orientation orientation; | |||||
BNDwidgetState state = BND_DEFAULT; | |||||
float offset = 0.0; | |||||
float size = 0.0; | |||||
ScrollBar(); | |||||
void draw(NVGcontext *vg) override; | |||||
void onDragStart(const event::DragStart &e) override; | |||||
void onDragMove(const event::DragMove &e) override; | |||||
void onDragEnd(const event::DragEnd &e) override; | |||||
}; | |||||
} // namespace rack |
@@ -1,46 +1,12 @@ | |||||
#pragma once | #pragma once | ||||
#include "widgets/OpaqueWidget.hpp" | |||||
#include "ui/common.hpp" | #include "ui/common.hpp" | ||||
#include "event.hpp" | |||||
#include "context.hpp" | |||||
#include "widgets/OpaqueWidget.hpp" | |||||
#include "ui/ScrollBar.hpp" | |||||
namespace rack { | namespace rack { | ||||
/** Parent must be a ScrollWidget */ | |||||
struct ScrollBar : OpaqueWidget { | |||||
enum Orientation { | |||||
VERTICAL, | |||||
HORIZONTAL | |||||
}; | |||||
Orientation orientation; | |||||
BNDwidgetState state = BND_DEFAULT; | |||||
float offset = 0.0; | |||||
float size = 0.0; | |||||
ScrollBar() { | |||||
box.size = math::Vec(BND_SCROLLBAR_WIDTH, BND_SCROLLBAR_HEIGHT); | |||||
} | |||||
void draw(NVGcontext *vg) override { | |||||
bndScrollBar(vg, 0.0, 0.0, box.size.x, box.size.y, state, offset, size); | |||||
} | |||||
void onDragStart(const event::DragStart &e) override { | |||||
state = BND_ACTIVE; | |||||
context()->window->cursorLock(); | |||||
} | |||||
void onDragMove(const event::DragMove &e) override; | |||||
void onDragEnd(const event::DragEnd &e) override { | |||||
state = BND_DEFAULT; | |||||
context()->window->cursorUnlock(); | |||||
} | |||||
}; | |||||
/** Handles a container with ScrollBar */ | /** Handles a container with ScrollBar */ | ||||
struct ScrollWidget : OpaqueWidget { | struct ScrollWidget : OpaqueWidget { | ||||
Widget *container; | Widget *container; | ||||
@@ -48,111 +14,13 @@ struct ScrollWidget : OpaqueWidget { | |||||
ScrollBar *verticalScrollBar; | ScrollBar *verticalScrollBar; | ||||
math::Vec offset; | math::Vec offset; | ||||
ScrollWidget() { | |||||
container = new Widget; | |||||
addChild(container); | |||||
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 scrollTo(math::Rect r) { | |||||
math::Rect bound = math::Rect::fromMinMax(r.getBottomRight().minus(box.size), r.pos); | |||||
offset = offset.clampBetween(bound); | |||||
} | |||||
void draw(NVGcontext *vg) override { | |||||
nvgScissor(vg, 0, 0, box.size.x, box.size.y); | |||||
Widget::draw(vg); | |||||
nvgResetScissor(vg); | |||||
} | |||||
void step() override { | |||||
Widget::step(); | |||||
// Clamp scroll offset | |||||
math::Vec containerCorner = container->getChildrenBoundingBox().getBottomRight(); | |||||
math::Rect containerBox = math::Rect(math::Vec(0, 0), containerCorner.minus(box.size)); | |||||
offset = offset.clamp(containerBox); | |||||
// Lock offset to top/left if no scrollbar will display | |||||
if (containerBox.size.x < 0.0) | |||||
offset.x = 0.0; | |||||
if (containerBox.size.y < 0.0) | |||||
offset.y = 0.0; | |||||
// Update the container's positions from the offset | |||||
container->box.pos = offset.neg().round(); | |||||
// Update scrollbar offsets and sizes | |||||
math::Vec viewportSize = container->getChildrenBoundingBox().getBottomRight(); | |||||
math::Vec scrollbarOffset = offset.div(viewportSize.minus(box.size)); | |||||
math::Vec scrollbarSize = box.size.div(viewportSize); | |||||
horizontalScrollBar->visible = (0.0 < scrollbarSize.x && scrollbarSize.x < 1.0); | |||||
verticalScrollBar->visible = (0.0 < scrollbarSize.y && scrollbarSize.y < 1.0); | |||||
horizontalScrollBar->offset = scrollbarOffset.x; | |||||
verticalScrollBar->offset = scrollbarOffset.y; | |||||
horizontalScrollBar->size = scrollbarSize.x; | |||||
verticalScrollBar->size = scrollbarSize.y; | |||||
// Resize scroll bars | |||||
math::Vec inner = math::Vec(box.size.x - verticalScrollBar->box.size.x, box.size.y - horizontalScrollBar->box.size.y); | |||||
horizontalScrollBar->box.pos.y = inner.y; | |||||
verticalScrollBar->box.pos.x = inner.x; | |||||
horizontalScrollBar->box.size.x = verticalScrollBar->visible ? inner.x : box.size.x; | |||||
verticalScrollBar->box.size.y = horizontalScrollBar->visible ? inner.y : box.size.y; | |||||
} | |||||
void onHover(const event::Hover &e) override { | |||||
// Scroll with arrow keys | |||||
if (!context()->event->selectedWidget) { | |||||
float arrowSpeed = 30.0; | |||||
if (context()->window->isShiftPressed() && context()->window->isModPressed()) | |||||
arrowSpeed /= 16.0; | |||||
else if (context()->window->isShiftPressed()) | |||||
arrowSpeed *= 4.0; | |||||
else if (context()->window->isModPressed()) | |||||
arrowSpeed /= 4.0; | |||||
if (glfwGetKey(context()->window->win, GLFW_KEY_LEFT) == GLFW_PRESS) { | |||||
offset.x -= arrowSpeed; | |||||
} | |||||
if (glfwGetKey(context()->window->win, GLFW_KEY_RIGHT) == GLFW_PRESS) { | |||||
offset.x += arrowSpeed; | |||||
} | |||||
if (glfwGetKey(context()->window->win, GLFW_KEY_UP) == GLFW_PRESS) { | |||||
offset.y -= arrowSpeed; | |||||
} | |||||
if (glfwGetKey(context()->window->win, GLFW_KEY_DOWN) == GLFW_PRESS) { | |||||
offset.y += arrowSpeed; | |||||
} | |||||
} | |||||
OpaqueWidget::onHover(e); | |||||
} | |||||
void onHoverScroll(const event::HoverScroll &e) override { | |||||
offset = offset.minus(e.scrollDelta); | |||||
e.consume(this); | |||||
} | |||||
ScrollWidget(); | |||||
void scrollTo(math::Rect r); | |||||
void draw(NVGcontext *vg) override; | |||||
void step() override; | |||||
void onHover(const event::Hover &e) override; | |||||
void onHoverScroll(const event::HoverScroll &e) override; | |||||
}; | }; | ||||
inline void ScrollBar::onDragMove(const event::DragMove &e) { | |||||
ScrollWidget *scrollWidget = dynamic_cast<ScrollWidget*>(parent); | |||||
assert(scrollWidget); | |||||
if (orientation == HORIZONTAL) | |||||
scrollWidget->offset.x += e.mouseDelta.x; | |||||
else | |||||
scrollWidget->offset.y += e.mouseDelta.y; | |||||
} | |||||
} // namespace rack | } // namespace rack |
@@ -4,6 +4,9 @@ | |||||
namespace rack { | namespace rack { | ||||
static const float KNOB_SENSITIVITY = 0.0015f; | |||||
void Knob::onButton(const event::Button &e) { | void Knob::onButton(const event::Button &e) { | ||||
float r = box.size.x / 2; | float r = box.size.x / 2; | ||||
math::Vec c = box.size.div(2); | math::Vec c = box.size.div(2); | ||||
@@ -1,17 +1,11 @@ | |||||
#include "app/ModuleBrowser.hpp" | #include "app/ModuleBrowser.hpp" | ||||
// TODO clean up | |||||
#include "window.hpp" | |||||
#include "helpers.hpp" | |||||
#include "event.hpp" | |||||
#include "ui/Quantity.hpp" | |||||
#include "ui/RadioButton.hpp" | |||||
#include "widgets/OpaqueWidget.hpp" | |||||
#include "widgets/TransparentWidget.hpp" | |||||
#include "widgets/ZoomWidget.hpp" | |||||
#include "ui/Label.hpp" | #include "ui/Label.hpp" | ||||
#include "ui/MenuOverlay.hpp" | |||||
#include "app/ModuleWidget.hpp" | |||||
#include "app/Scene.hpp" | #include "app/Scene.hpp" | ||||
#include "ui/List.hpp" | |||||
#include "ui/TextField.hpp" | |||||
#include "ui/SequentialLayout.hpp" | |||||
#include "widgets/ObstructWidget.hpp" | |||||
#include "widgets/ZoomWidget.hpp" | |||||
#include "plugin.hpp" | #include "plugin.hpp" | ||||
#include "context.hpp" | #include "context.hpp" | ||||
@@ -27,75 +21,122 @@ static std::string sAuthorFilter; | |||||
static std::string sTagFilter; | static std::string sTagFilter; | ||||
struct ModuleWidgetWrapper : ObstructWidget { | |||||
struct ModuleBox : OpaqueWidget { | |||||
Model *model; | Model *model; | ||||
void onDragDrop(const event::DragDrop &e) override { | |||||
if (e.origin == this) { | |||||
void setModel(Model *model) { | |||||
this->model = model; | |||||
Widget *transparentWidget = new TransparentWidget; | |||||
addChild(transparentWidget); | |||||
ZoomWidget *zoomWidget = new ZoomWidget; | |||||
zoomWidget->setZoom(0.5); | |||||
transparentWidget->addChild(zoomWidget); | |||||
ModuleWidget *moduleWidget = model->createModuleWidgetNull(); | |||||
zoomWidget->addChild(moduleWidget); | |||||
box.size = math::Vec(moduleWidget->box.size.x, RACK_GRID_SIZE.y).mult(zoomWidget->zoom).ceil(); | |||||
math::Vec p; | |||||
p.y = box.size.y; | |||||
box.size.y += 40.0; | |||||
box.size.x = std::max(box.size.x, 70.f); | |||||
Label *nameLabel = new Label; | |||||
nameLabel->text = model->name; | |||||
nameLabel->box.pos = p; | |||||
p.y += nameLabel->box.size.y; | |||||
addChild(nameLabel); | |||||
Label *pluginLabel = new Label; | |||||
pluginLabel->text = model->plugin->name; | |||||
pluginLabel->box.pos = p; | |||||
p.y += pluginLabel->box.size.y; | |||||
addChild(pluginLabel); | |||||
} | |||||
void draw(NVGcontext *vg) override { | |||||
OpaqueWidget::draw(vg); | |||||
if (context()->event->hoveredWidget == this) { | |||||
nvgBeginPath(vg); | |||||
nvgRect(vg, 0.0, 0.0, box.size.x, box.size.y); | |||||
nvgFillColor(vg, nvgRGBAf(1, 1, 1, 0.25)); | |||||
nvgFill(vg); | |||||
} | |||||
} | |||||
void onButton(const event::Button &e) override { | |||||
if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT) { | |||||
// Create module | // Create module | ||||
ModuleWidget *moduleWidget = model->createModuleWidget(); | ModuleWidget *moduleWidget = model->createModuleWidget(); | ||||
assert(moduleWidget); | assert(moduleWidget); | ||||
context()->scene->rackWidget->addModuleAtMouse(moduleWidget); | context()->scene->rackWidget->addModuleAtMouse(moduleWidget); | ||||
// This is a bit nonstandard/unsupported usage, but pretend the moduleWidget was clicked so it can be dragged in the RackWidget | |||||
e.consume(moduleWidget); | |||||
// Close Module Browser | // Close Module Browser | ||||
MenuOverlay *menuOverlay = getAncestorOfType<MenuOverlay>(); | |||||
menuOverlay->requestedDelete = true; | |||||
ModuleBrowser *moduleBrowser = getAncestorOfType<ModuleBrowser>(); | |||||
moduleBrowser->visible = false; | |||||
} | } | ||||
OpaqueWidget::onButton(e); | |||||
} | } | ||||
}; | }; | ||||
struct ModuleBrowser : OpaqueWidget { | |||||
SequentialLayout *moduleLayout; | |||||
ModuleBrowser() { | |||||
moduleLayout = new SequentialLayout; | |||||
moduleLayout->spacing = math::Vec(10, 10); | |||||
addChild(moduleLayout); | |||||
ModuleBrowser::ModuleBrowser() { | |||||
moduleScroll = new ScrollWidget; | |||||
addChild(moduleScroll); | |||||
for (Plugin *plugin : plugin::plugins) { | |||||
for (Model *model : plugin->models) { | |||||
ModuleWidgetWrapper *wrapper = new ModuleWidgetWrapper; | |||||
wrapper->model = model; | |||||
moduleLayout->addChild(wrapper); | |||||
moduleLayout = new SequentialLayout; | |||||
moduleLayout->spacing = math::Vec(10, 10); | |||||
moduleScroll->container->addChild(moduleLayout); | |||||
ZoomWidget *zoomWidget = new ZoomWidget; | |||||
zoomWidget->setZoom(0.5); | |||||
wrapper->addChild(zoomWidget); | |||||
ModuleWidget *moduleWidget = model->createModuleWidgetNull(); | |||||
zoomWidget->addChild(moduleWidget); | |||||
wrapper->box.size = moduleWidget->box.size.mult(zoomWidget->zoom); | |||||
} | |||||
for (Plugin *plugin : plugin::plugins) { | |||||
for (Model *model : plugin->models) { | |||||
ModuleBox *moduleBox = new ModuleBox; | |||||
moduleBox->setModel(model); | |||||
moduleLayout->addChild(moduleBox); | |||||
} | } | ||||
} | } | ||||
} | |||||
void step() override { | |||||
assert(parent); | |||||
box = parent->box.zeroPos().grow(math::Vec(-50, -50)); | |||||
moduleLayout->box.size = box.size; | |||||
void ModuleBrowser::step() { | |||||
// TODO resize sidebar | |||||
float sidebarWidth = 300.0; | |||||
OpaqueWidget::step(); | |||||
} | |||||
moduleScroll->box.pos.x = sidebarWidth; | |||||
moduleScroll->box.size.x = box.size.x - sidebarWidth; | |||||
moduleScroll->box.size.y = box.size.y; | |||||
moduleLayout->box.size.x = moduleScroll->box.size.x; | |||||
moduleLayout->box.size.y = moduleLayout->getChildrenBoundingBox().getBottomRight().y; | |||||
void draw(NVGcontext *vg) override { | |||||
bndTooltipBackground(vg, 0.0, 0.0, box.size.x, box.size.y); | |||||
Widget::draw(vg); | |||||
} | |||||
}; | |||||
OpaqueWidget::step(); | |||||
} | |||||
void ModuleBrowser::draw(NVGcontext *vg) { | |||||
bndMenuBackground(vg, 0.0, 0.0, box.size.x, box.size.y, 0); | |||||
Widget::draw(vg); | |||||
} | |||||
void ModuleBrowser::onHoverKey(const event::HoverKey &e) { | |||||
if (e.action == GLFW_PRESS) { | |||||
switch (e.key) { | |||||
case GLFW_KEY_ESCAPE: { | |||||
// Close menu | |||||
this->visible = false; | |||||
e.consume(this); | |||||
} break; | |||||
} | |||||
} | |||||
// Global functions | |||||
if (!e.getConsumed()) | |||||
OpaqueWidget::onHoverKey(e); | |||||
} | |||||
void moduleBrowserCreate() { | |||||
MenuOverlay *overlay = new MenuOverlay; | |||||
ModuleBrowser *moduleBrowser = new ModuleBrowser; | |||||
overlay->addChild(moduleBrowser); | |||||
context()->scene->addChild(overlay); | |||||
} | |||||
// Global functions | |||||
json_t *moduleBrowserToJson() { | json_t *moduleBrowserToJson() { | ||||
json_t *rootJ = json_object(); | json_t *rootJ = json_object(); | ||||
@@ -284,7 +284,7 @@ void ModuleWidget::draw(NVGcontext *vg) { | |||||
if (module && module->bypass) { | if (module && module->bypass) { | ||||
nvgGlobalAlpha(vg, 0.5); | nvgGlobalAlpha(vg, 0.5); | ||||
} | } | ||||
nvgScissor(vg, 0, 0, box.size.x, box.size.y); | |||||
// nvgScissor(vg, 0, 0, box.size.x, box.size.y); | |||||
Widget::draw(vg); | Widget::draw(vg); | ||||
// Power meter | // Power meter | ||||
@@ -312,7 +312,7 @@ void ModuleWidget::draw(NVGcontext *vg) { | |||||
nvgFill(vg); | nvgFill(vg); | ||||
} | } | ||||
nvgResetScissor(vg); | |||||
// nvgResetScissor(vg); | |||||
} | } | ||||
void ModuleWidget::drawShadow(NVGcontext *vg) { | void ModuleWidget::drawShadow(NVGcontext *vg) { | ||||
@@ -566,7 +566,7 @@ void RackWidget::onButton(const event::Button &e) { | |||||
OpaqueWidget::onButton(e); | OpaqueWidget::onButton(e); | ||||
if (e.getConsumed() == this) { | if (e.getConsumed() == this) { | ||||
if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) { | if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) { | ||||
moduleBrowserCreate(); | |||||
context()->scene->moduleBrowser->visible = true; | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -26,6 +26,10 @@ Scene::Scene() { | |||||
toolbar = new Toolbar; | toolbar = new Toolbar; | ||||
addChild(toolbar); | addChild(toolbar); | ||||
scrollWidget->box.pos.y = toolbar->box.size.y; | scrollWidget->box.pos.y = toolbar->box.size.y; | ||||
moduleBrowser = new ModuleBrowser; | |||||
moduleBrowser->visible = false; | |||||
addChild(moduleBrowser); | |||||
} | } | ||||
Scene::~Scene() { | Scene::~Scene() { | ||||
@@ -45,6 +49,7 @@ void Scene::step() { | |||||
OpaqueWidget::step(); | OpaqueWidget::step(); | ||||
zoomWidget->box.size = rackWidget->box.size.mult(zoomWidget->zoom); | zoomWidget->box.size = rackWidget->box.size.mult(zoomWidget->zoom); | ||||
moduleBrowser->box.size = box.size; | |||||
// Request latest version from server | // Request latest version from server | ||||
if (!devMode && checkVersion && !checkedVersion) { | if (!devMode && checkVersion && !checkedVersion) { | ||||
@@ -112,7 +117,7 @@ void Scene::onHoverKey(const event::HoverKey &e) { | |||||
} break; | } break; | ||||
case GLFW_KEY_ENTER: | case GLFW_KEY_ENTER: | ||||
case GLFW_KEY_KP_ENTER: { | case GLFW_KEY_KP_ENTER: { | ||||
moduleBrowserCreate(); | |||||
moduleBrowser->visible = true; | |||||
e.consume(this); | e.consume(this); | ||||
} break; | } break; | ||||
case GLFW_KEY_F11: { | case GLFW_KEY_F11: { | ||||
@@ -560,7 +560,7 @@ Toolbar::Toolbar() { | |||||
} | } | ||||
void Toolbar::draw(NVGcontext *vg) { | void Toolbar::draw(NVGcontext *vg) { | ||||
bndMenuBackground(vg, 0.0, 0.0, box.size.x, box.size.y, 0); | |||||
bndMenuBackground(vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_ALL); | |||||
bndBevel(vg, 0.0, 0.0, box.size.x, box.size.y); | bndBevel(vg, 0.0, 0.0, box.size.x, box.size.y); | ||||
Widget::draw(vg); | Widget::draw(vg); | ||||
@@ -79,12 +79,12 @@ int main(int argc, char *argv[]) { | |||||
plugin::init(devMode); | plugin::init(devMode); | ||||
// Initialize app | // Initialize app | ||||
context()->engine = new Engine; | |||||
context()->event = new event::State; | context()->event = new event::State; | ||||
context()->window = new Window; | |||||
context()->engine = new Engine; | |||||
context()->scene = new Scene; | context()->scene = new Scene; | ||||
context()->scene->devMode = devMode; | context()->scene->devMode = devMode; | ||||
context()->event->rootWidget = context()->scene; | context()->event->rootWidget = context()->scene; | ||||
context()->window = new Window; | |||||
settings::load(asset::user("settings.json")); | settings::load(asset::user("settings.json")); | ||||
if (patchFile.empty()) { | if (patchFile.empty()) { | ||||
@@ -120,10 +120,10 @@ int main(int argc, char *argv[]) { | |||||
context()->scene = NULL; | context()->scene = NULL; | ||||
delete context()->event; | delete context()->event; | ||||
context()->event = NULL; | context()->event = NULL; | ||||
delete context()->window; | |||||
context()->window = NULL; | |||||
delete context()->engine; | delete context()->engine; | ||||
context()->engine = NULL; | context()->engine = NULL; | ||||
delete context()->window; | |||||
context()->window = NULL; | |||||
// Destroy environment | // Destroy environment | ||||
plugin::destroy(); | plugin::destroy(); | ||||
@@ -0,0 +1,41 @@ | |||||
#include "ui/ScrollBar.hpp" | |||||
#include "ui/ScrollWidget.hpp" | |||||
#include "context.hpp" | |||||
#include "window.hpp" | |||||
namespace rack { | |||||
static const float SCROLLBAR_SENSITIVITY = 2.f; | |||||
ScrollBar::ScrollBar() { | |||||
box.size = math::Vec(BND_SCROLLBAR_WIDTH, BND_SCROLLBAR_HEIGHT); | |||||
} | |||||
void ScrollBar::draw(NVGcontext *vg) { | |||||
bndScrollBar(vg, 0.0, 0.0, box.size.x, box.size.y, state, offset, size); | |||||
} | |||||
void ScrollBar::onDragStart(const event::DragStart &e) { | |||||
state = BND_ACTIVE; | |||||
context()->window->cursorLock(); | |||||
} | |||||
void ScrollBar::onDragMove(const event::DragMove &e) { | |||||
ScrollWidget *scrollWidget = dynamic_cast<ScrollWidget*>(parent); | |||||
assert(scrollWidget); | |||||
if (orientation == HORIZONTAL) | |||||
scrollWidget->offset.x += SCROLLBAR_SENSITIVITY * e.mouseDelta.x; | |||||
else | |||||
scrollWidget->offset.y += SCROLLBAR_SENSITIVITY * e.mouseDelta.y; | |||||
} | |||||
void ScrollBar::onDragEnd(const event::DragEnd &e) { | |||||
state = BND_DEFAULT; | |||||
context()->window->cursorUnlock(); | |||||
} | |||||
} // namespace rack |
@@ -0,0 +1,105 @@ | |||||
#include "ui/ScrollWidget.hpp" | |||||
#include "context.hpp" | |||||
#include "event.hpp" | |||||
namespace rack { | |||||
ScrollWidget::ScrollWidget() { | |||||
container = new Widget; | |||||
addChild(container); | |||||
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::scrollTo(math::Rect r) { | |||||
math::Rect bound = math::Rect::fromMinMax(r.getBottomRight().minus(box.size), r.pos); | |||||
offset = offset.clampBetween(bound); | |||||
} | |||||
void ScrollWidget::draw(NVGcontext *vg) { | |||||
nvgScissor(vg, 0, 0, box.size.x, box.size.y); | |||||
Widget::draw(vg); | |||||
nvgResetScissor(vg); | |||||
} | |||||
void ScrollWidget::step() { | |||||
Widget::step(); | |||||
// Clamp scroll offset | |||||
math::Vec containerCorner = container->getChildrenBoundingBox().getBottomRight(); | |||||
math::Rect containerBox = math::Rect(math::Vec(0, 0), containerCorner.minus(box.size)); | |||||
offset = offset.clamp(containerBox); | |||||
// Lock offset to top/left if no scrollbar will display | |||||
if (containerBox.size.x < 0.0) | |||||
offset.x = 0.0; | |||||
if (containerBox.size.y < 0.0) | |||||
offset.y = 0.0; | |||||
// Update the container's positions from the offset | |||||
container->box.pos = offset.neg().round(); | |||||
// Update scrollbar offsets and sizes | |||||
math::Vec viewportSize = container->getChildrenBoundingBox().getBottomRight(); | |||||
math::Vec scrollbarOffset = offset.div(viewportSize.minus(box.size)); | |||||
math::Vec scrollbarSize = box.size.div(viewportSize); | |||||
horizontalScrollBar->visible = (0.0 < scrollbarSize.x && scrollbarSize.x < 1.0); | |||||
verticalScrollBar->visible = (0.0 < scrollbarSize.y && scrollbarSize.y < 1.0); | |||||
horizontalScrollBar->offset = scrollbarOffset.x; | |||||
verticalScrollBar->offset = scrollbarOffset.y; | |||||
horizontalScrollBar->size = scrollbarSize.x; | |||||
verticalScrollBar->size = scrollbarSize.y; | |||||
// Resize scroll bars | |||||
math::Vec inner = math::Vec(box.size.x - verticalScrollBar->box.size.x, box.size.y - horizontalScrollBar->box.size.y); | |||||
horizontalScrollBar->box.pos.y = inner.y; | |||||
verticalScrollBar->box.pos.x = inner.x; | |||||
horizontalScrollBar->box.size.x = verticalScrollBar->visible ? inner.x : box.size.x; | |||||
verticalScrollBar->box.size.y = horizontalScrollBar->visible ? inner.y : box.size.y; | |||||
} | |||||
void ScrollWidget::onHover(const event::Hover &e) { | |||||
// Scroll with arrow keys | |||||
if (!context()->event->selectedWidget) { | |||||
float arrowSpeed = 30.0; | |||||
if (context()->window->isShiftPressed() && context()->window->isModPressed()) | |||||
arrowSpeed /= 16.0; | |||||
else if (context()->window->isShiftPressed()) | |||||
arrowSpeed *= 4.0; | |||||
else if (context()->window->isModPressed()) | |||||
arrowSpeed /= 4.0; | |||||
if (glfwGetKey(context()->window->win, GLFW_KEY_LEFT) == GLFW_PRESS) { | |||||
offset.x -= arrowSpeed; | |||||
} | |||||
if (glfwGetKey(context()->window->win, GLFW_KEY_RIGHT) == GLFW_PRESS) { | |||||
offset.x += arrowSpeed; | |||||
} | |||||
if (glfwGetKey(context()->window->win, GLFW_KEY_UP) == GLFW_PRESS) { | |||||
offset.y -= arrowSpeed; | |||||
} | |||||
if (glfwGetKey(context()->window->win, GLFW_KEY_DOWN) == GLFW_PRESS) { | |||||
offset.y += arrowSpeed; | |||||
} | |||||
} | |||||
OpaqueWidget::onHover(e); | |||||
} | |||||
void ScrollWidget::onHoverScroll(const event::HoverScroll &e) { | |||||
offset = offset.minus(e.scrollDelta); | |||||
e.consume(this); | |||||
} | |||||
} // namespace rack |