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