@@ -184,6 +184,18 @@ struct Vec { | |||
Vec() {} | |||
Vec(float x, float y) : x(x), y(y) {} | |||
float get(int index) const { | |||
return (index == 0) ? x : y; | |||
} | |||
float& get(int index) { | |||
return (index == 0) ? x : y; | |||
} | |||
void set(int index, float value) { | |||
if (index == 0) | |||
x = value; | |||
else | |||
y = value; | |||
} | |||
/** Negates the vector. | |||
Equivalent to a reflection across the `y = -x` line. | |||
*/ | |||
@@ -9,19 +9,17 @@ namespace ui { | |||
/** Parent must be a ScrollWidget */ | |||
struct ScrollBar : widget::OpaqueWidget { | |||
enum Orientation { | |||
VERTICAL, | |||
HORIZONTAL | |||
}; | |||
Orientation orientation; | |||
float offset = 0.0; | |||
float size = 0.0; | |||
struct Internal; | |||
Internal* internal; | |||
bool vertical = false; | |||
ScrollBar(); | |||
~ScrollBar(); | |||
void draw(const DrawArgs& args) override; | |||
void onDragStart(const event::DragStart& e) override; | |||
void onDragMove(const event::DragMove& e) override; | |||
void onDragEnd(const event::DragEnd& e) override; | |||
void onDragMove(const event::DragMove& e) override; | |||
}; | |||
@@ -10,10 +10,15 @@ namespace ui { | |||
/** Handles a container with ScrollBar */ | |||
struct ScrollWidget : widget::OpaqueWidget { | |||
struct Internal; | |||
Internal* internal; | |||
widget::Widget* container; | |||
ScrollBar* horizontalScrollBar; | |||
ScrollBar* verticalScrollBar; | |||
math::Vec offset; | |||
math::Rect containerBox; | |||
ScrollWidget(); | |||
void scrollTo(math::Rect r); | |||
@@ -33,23 +33,21 @@ struct Widget { | |||
virtual ~Widget(); | |||
math::Rect getBox(); | |||
void setBox(math::Rect box); | |||
math::Rect getBox() { | |||
return box; | |||
} | |||
math::Vec getPosition(); | |||
void setPosition(math::Vec pos); | |||
math::Vec getPosition() { | |||
return box.pos; | |||
} | |||
math::Vec getSize(); | |||
void setSize(math::Vec size); | |||
math::Vec getSize() { | |||
return box.size; | |||
bool isVisible(); | |||
void setVisible(bool visible); | |||
void show() { | |||
setVisible(true); | |||
} | |||
void show(); | |||
void hide(); | |||
bool isVisible() { | |||
return visible; | |||
void hide() { | |||
setVisible(false); | |||
} | |||
void requestDelete(); | |||
virtual math::Rect getChildrenBoundingBox(); | |||
@@ -8,40 +8,63 @@ namespace rack { | |||
namespace ui { | |||
// Internal not currently used | |||
ScrollBar::ScrollBar() { | |||
box.size = math::Vec(BND_SCROLLBAR_WIDTH, BND_SCROLLBAR_HEIGHT); | |||
} | |||
ScrollBar::~ScrollBar() { | |||
} | |||
void ScrollBar::draw(const DrawArgs& args) { | |||
ScrollWidget* sw = dynamic_cast<ScrollWidget*>(parent); | |||
assert(sw); | |||
BNDwidgetState state = BND_DEFAULT; | |||
if (APP->event->hoveredWidget == this) | |||
if (APP->event->getHoveredWidget() == this) | |||
state = BND_HOVER; | |||
if (APP->event->draggedWidget == this) | |||
if (APP->event->getDraggedWidget() == this) | |||
state = BND_ACTIVE; | |||
bndScrollBar(args.vg, 0.0, 0.0, box.size.x, box.size.y, state, offset, size); | |||
float offsetBound = sw->containerBox.size.get(vertical) - sw->box.size.get(vertical); | |||
// The handle position relative to the scrollbar. [0, 1] | |||
float scrollBarOffset = (sw->offset.get(vertical) - sw->containerBox.pos.get(vertical)) / offsetBound; | |||
// The handle size relative to the scrollbar. [0, 1] | |||
float scrollBarSize = sw->box.size.get(vertical) / sw->containerBox.size.get(vertical); | |||
bndScrollBar(args.vg, 0.0, 0.0, box.size.x, box.size.y, state, scrollBarOffset, scrollBarSize); | |||
} | |||
void ScrollBar::onDragStart(const event::DragStart& e) { | |||
if (e.button != GLFW_MOUSE_BUTTON_LEFT) | |||
return; | |||
if (e.button == GLFW_MOUSE_BUTTON_LEFT) { | |||
APP->window->cursorLock(); | |||
} | |||
} | |||
APP->window->cursorLock(); | |||
void ScrollBar::onDragEnd(const event::DragEnd& e) { | |||
if (e.button == GLFW_MOUSE_BUTTON_LEFT) { | |||
APP->window->cursorUnlock(); | |||
} | |||
} | |||
void ScrollBar::onDragMove(const event::DragMove& e) { | |||
const float sensitivity = 1.f; | |||
ScrollWidget* sw = dynamic_cast<ScrollWidget*>(parent); | |||
assert(sw); | |||
ScrollWidget* scrollWidget = dynamic_cast<ScrollWidget*>(parent); | |||
assert(scrollWidget); | |||
if (orientation == HORIZONTAL) | |||
scrollWidget->offset.x += sensitivity * e.mouseDelta.x; | |||
else | |||
scrollWidget->offset.y += sensitivity * e.mouseDelta.y; | |||
} | |||
// TODO | |||
// float offsetBound = sw->containerBox.size.get(vertical) - sw->box.size.get(vertical); | |||
// float scrollBarSize = sw->box.size.get(vertical) / sw->containerBox.size.get(vertical); | |||
void ScrollBar::onDragEnd(const event::DragEnd& e) { | |||
APP->window->cursorUnlock(); | |||
const float sensitivity = 1.f; | |||
float offsetDelta = e.mouseDelta.get(vertical); | |||
offsetDelta *= sensitivity; | |||
sw->offset.get(vertical) += offsetDelta; | |||
} | |||
@@ -6,37 +6,43 @@ namespace rack { | |||
namespace ui { | |||
// Internal not currently used | |||
ScrollWidget::ScrollWidget() { | |||
container = new widget::Widget; | |||
addChild(container); | |||
horizontalScrollBar = new ScrollBar; | |||
horizontalScrollBar->orientation = ScrollBar::HORIZONTAL; | |||
horizontalScrollBar->visible = false; | |||
horizontalScrollBar->vertical = false; | |||
horizontalScrollBar->hide(); | |||
addChild(horizontalScrollBar); | |||
verticalScrollBar = new ScrollBar; | |||
verticalScrollBar->orientation = ScrollBar::VERTICAL; | |||
verticalScrollBar->visible = false; | |||
verticalScrollBar->vertical = true; | |||
verticalScrollBar->hide(); | |||
addChild(verticalScrollBar); | |||
} | |||
void ScrollWidget::scrollTo(math::Rect r) { | |||
math::Rect bound = math::Rect::fromMinMax(r.getBottomRight().minus(box.size), r.pos); | |||
offset = offset.clampSafe(bound); | |||
} | |||
void ScrollWidget::draw(const DrawArgs& args) { | |||
nvgScissor(args.vg, RECT_ARGS(args.clipBox)); | |||
Widget::draw(args); | |||
nvgResetScissor(args.vg); | |||
} | |||
void ScrollWidget::step() { | |||
Widget::step(); | |||
// Clamp scroll offset | |||
math::Rect containerBox = container->getChildrenBoundingBox(); | |||
containerBox = container->getChildrenBoundingBox(); | |||
math::Rect offsetBounds = containerBox; | |||
offsetBounds.size = offsetBounds.size.minus(box.size); | |||
offset = offset.clamp(offsetBounds); | |||
@@ -44,25 +50,19 @@ void ScrollWidget::step() { | |||
// Update the container's position from the offset | |||
container->box.pos = offset.neg().round(); | |||
// Update scrollbar offsets and sizes | |||
math::Vec scrollbarOffset = offset.minus(containerBox.pos).div(offsetBounds.size); | |||
math::Vec scrollbarSize = box.size.div(containerBox.size); | |||
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; | |||
// Make scrollbars visible only if there is a positive range to scroll. | |||
horizontalScrollBar->setVisible(offsetBounds.size.x > 0.f); | |||
verticalScrollBar->setVisible(offsetBounds.size.y > 0.f); | |||
// Reposition and resize scroll bars | |||
math::Vec inner = box.size.minus(math::Vec(verticalScrollBar->box.size.x, 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; | |||
horizontalScrollBar->box.size.x = verticalScrollBar->isVisible() ? inner.x : box.size.x; | |||
verticalScrollBar->box.size.y = horizontalScrollBar->isVisible() ? inner.y : box.size.y; | |||
} | |||
void ScrollWidget::onButton(const event::Button& e) { | |||
OpaqueWidget::onButton(e); | |||
if (e.isConsumed()) | |||
@@ -77,12 +77,14 @@ void ScrollWidget::onButton(const event::Button& e) { | |||
} | |||
} | |||
void ScrollWidget::onDragStart(const event::DragStart& e) { | |||
if (e.button == GLFW_MOUSE_BUTTON_LEFT || e.button == GLFW_MOUSE_BUTTON_MIDDLE) { | |||
e.consume(this); | |||
} | |||
} | |||
void ScrollWidget::onDragMove(const event::DragMove& e) { | |||
// Scroll only if the scrollbars are visible | |||
if (!(horizontalScrollBar->visible || verticalScrollBar->visible)) | |||
@@ -91,6 +93,7 @@ void ScrollWidget::onDragMove(const event::DragMove& e) { | |||
offset = offset.minus(e.mouseDelta); | |||
} | |||
void ScrollWidget::onHoverScroll(const event::HoverScroll& e) { | |||
OpaqueWidget::onHoverScroll(e); | |||
if (e.isConsumed()) | |||
@@ -112,6 +115,7 @@ void ScrollWidget::onHoverScroll(const event::HoverScroll& e) { | |||
e.consume(this); | |||
} | |||
void ScrollWidget::onHoverKey(const event::HoverKey& e) { | |||
OpaqueWidget::onHoverKey(e); | |||
if (e.isConsumed()) | |||
@@ -14,11 +14,23 @@ Widget::~Widget() { | |||
clearChildren(); | |||
} | |||
math::Rect Widget::getBox() { | |||
return box; | |||
} | |||
void Widget::setBox(math::Rect box) { | |||
setPosition(box.pos); | |||
setSize(box.size); | |||
} | |||
math::Vec Widget::getPosition() { | |||
return box.pos; | |||
} | |||
void Widget::setPosition(math::Vec pos) { | |||
if (pos.isEqual(box.pos)) | |||
return; | |||
@@ -28,6 +40,12 @@ void Widget::setPosition(math::Vec pos) { | |||
onReposition(eReposition); | |||
} | |||
math::Vec Widget::getSize() { | |||
return box.size; | |||
} | |||
void Widget::setSize(math::Vec size) { | |||
if (size.isEqual(box.size)) | |||
return; | |||
@@ -37,28 +55,34 @@ void Widget::setSize(math::Vec size) { | |||
onResize(eResize); | |||
} | |||
void Widget::show() { | |||
if (visible) | |||
return; | |||
visible = true; | |||
// Trigger Show event | |||
event::Show eShow; | |||
onShow(eShow); | |||
bool Widget::isVisible() { | |||
return visible; | |||
} | |||
void Widget::hide() { | |||
if (!visible) | |||
void Widget::setVisible(bool visible) { | |||
if (visible == this->visible) | |||
return; | |||
visible = false; | |||
// Trigger Hide event | |||
event::Hide eHide; | |||
onHide(eHide); | |||
this->visible = visible; | |||
if (visible) { | |||
// Trigger Show event | |||
event::Show eShow; | |||
onShow(eShow); | |||
} | |||
else { | |||
// Trigger Hide event | |||
event::Hide eHide; | |||
onHide(eHide); | |||
} | |||
} | |||
void Widget::requestDelete() { | |||
requestedDelete = true; | |||
} | |||
math::Rect Widget::getChildrenBoundingBox() { | |||
math::Vec min = math::Vec(INFINITY, INFINITY); | |||
math::Vec max = math::Vec(-INFINITY, -INFINITY); | |||
@@ -71,6 +95,7 @@ math::Rect Widget::getChildrenBoundingBox() { | |||
return math::Rect::fromMinMax(min, max); | |||
} | |||
math::Vec Widget::getRelativeOffset(math::Vec v, Widget* relative) { | |||
if (this == relative) { | |||
return v; | |||
@@ -82,6 +107,7 @@ math::Vec Widget::getRelativeOffset(math::Vec v, Widget* relative) { | |||
return v; | |||
} | |||
math::Rect Widget::getViewport(math::Rect r) { | |||
math::Rect bound; | |||
if (parent) { | |||
@@ -94,6 +120,7 @@ math::Rect Widget::getViewport(math::Rect r) { | |||
return r.clamp(bound); | |||
} | |||
void Widget::addChild(Widget* child) { | |||
assert(child); | |||
assert(!child->parent); | |||
@@ -104,6 +131,7 @@ void Widget::addChild(Widget* child) { | |||
child->onAdd(eAdd); | |||
} | |||
void Widget::addChildBottom(Widget* child) { | |||
assert(child); | |||
assert(!child->parent); | |||
@@ -114,6 +142,7 @@ void Widget::addChildBottom(Widget* child) { | |||
child->onAdd(eAdd); | |||
} | |||
void Widget::removeChild(Widget* child) { | |||
assert(child); | |||
// Make sure `this` is the child's parent | |||
@@ -131,6 +160,7 @@ void Widget::removeChild(Widget* child) { | |||
child->parent = NULL; | |||
} | |||
void Widget::clearChildren() { | |||
for (Widget* child : children) { | |||
// Trigger Remove event | |||
@@ -143,6 +173,7 @@ void Widget::clearChildren() { | |||
children.clear(); | |||
} | |||
void Widget::step() { | |||
for (auto it = children.begin(); it != children.end();) { | |||
Widget* child = *it; | |||
@@ -163,6 +194,7 @@ void Widget::step() { | |||
} | |||
} | |||
void Widget::draw(const DrawArgs& args) { | |||
// Iterate children | |||
for (Widget* child : children) { | |||