@@ -18,6 +18,7 @@ struct ParamWidget : widget::OpaqueWidget { | |||
void step() override; | |||
void draw(const DrawArgs &args) override; | |||
void onButton(const widget::ButtonEvent &e) override; | |||
void onDoubleClick(const widget::DoubleClickEvent &e) override; | |||
void onEnter(const widget::EnterEvent &e) override; | |||
@@ -1,5 +1,5 @@ | |||
#pragma once | |||
#include "widget/OverlayWidget.hpp" | |||
#include "widget/OpaqueWidget.hpp" | |||
#include "ui/common.hpp" | |||
@@ -8,7 +8,7 @@ namespace ui { | |||
/** Deletes itself from parent when clicked */ | |||
struct MenuOverlay : widget::OverlayWidget { | |||
struct MenuOverlay : widget::OpaqueWidget { | |||
void step() override; | |||
void onButton(const widget::ButtonEvent &e) override; | |||
void onHoverKey(const widget::HoverKeyEvent &e) override; | |||
@@ -22,8 +22,8 @@ struct TextField : widget::OpaqueWidget { | |||
TextField(); | |||
void draw(const DrawArgs &args) override; | |||
void onButton(const widget::ButtonEvent &e) override; | |||
void onHover(const widget::HoverEvent &e) override; | |||
void onButton(const widget::ButtonEvent &e) override; | |||
void onEnter(const widget::EnterEvent &e) override; | |||
void onSelect(const widget::SelectEvent &e) override; | |||
void onSelectText(const widget::SelectTextEvent &e) override; | |||
@@ -1,31 +0,0 @@ | |||
#pragma once | |||
#include "widget/Widget.hpp" | |||
namespace rack { | |||
namespace widget { | |||
/** A Widget that consumes recursing events without giving a chance for children to consume. | |||
*/ | |||
struct ObstructWidget : Widget { | |||
void onHover(const HoverEvent &e) override { | |||
e.consume(this); | |||
} | |||
void onButton(const ButtonEvent &e) override { | |||
e.consume(this); | |||
} | |||
void onHoverKey(const HoverKeyEvent &e) override { | |||
e.consume(this); | |||
} | |||
void onHoverText(const HoverTextEvent &e) override { | |||
e.consume(this); | |||
} | |||
void onDragHover(const DragHoverEvent &e) override { | |||
e.consume(this); | |||
} | |||
}; | |||
} // namespace widget | |||
} // namespace rack |
@@ -6,35 +6,43 @@ namespace rack { | |||
namespace widget { | |||
/** A Widget that consumes recursing events but gives a chance for children to consume first. | |||
You can of course override the events. | |||
You may also call OpaqueWidget::on*() from the overridden method to continue recursing/consuming the event. | |||
/** A Widget that stops propagation of all recursive PositionEvents but gives a chance for children to consume first. | |||
Remember to call these methods in your subclass if you wish to preserve default OpaqueWidget behavior. | |||
*/ | |||
struct OpaqueWidget : Widget { | |||
void onHover(const HoverEvent &e) override { | |||
Widget::onHover(e); | |||
if (!e.getConsumed()) | |||
e.consume(this); | |||
e.stopPropagating(); | |||
// Consume if not consumed by child | |||
if (!e.getTarget()) | |||
e.setTarget(this); | |||
} | |||
void onButton(const ButtonEvent &e) override { | |||
Widget::onButton(e); | |||
if (!e.getConsumed()) | |||
e.consume(this); | |||
e.stopPropagating(); | |||
// Consume if not consumed by child | |||
if (!e.getTarget()) | |||
e.setTarget(this); | |||
} | |||
void onHoverKey(const HoverKeyEvent &e) override { | |||
Widget::onHoverKey(e); | |||
if (!e.getConsumed()) | |||
e.consume(this); | |||
e.stopPropagating(); | |||
} | |||
void onHoverText(const HoverTextEvent &e) override { | |||
Widget::onHoverText(e); | |||
if (!e.getConsumed()) | |||
e.consume(this); | |||
e.stopPropagating(); | |||
} | |||
void onHoverScroll(const HoverScrollEvent &e) override { | |||
Widget::onHoverScroll(e); | |||
e.stopPropagating(); | |||
} | |||
void onDragHover(const DragHoverEvent &e) override { | |||
Widget::onDragHover(e); | |||
if (!e.getConsumed()) | |||
e.consume(this); | |||
e.stopPropagating(); | |||
} | |||
void onPathDrop(const PathDropEvent &e) override { | |||
Widget::onPathDrop(e); | |||
e.stopPropagating(); | |||
} | |||
}; | |||
@@ -1,25 +0,0 @@ | |||
#pragma once | |||
#include "widget/OpaqueWidget.hpp" | |||
namespace rack { | |||
namespace widget { | |||
/** Like OpaqueWidget but consumes even more events. */ | |||
struct OverlayWidget : OpaqueWidget { | |||
void onHoverScroll(const HoverScrollEvent &e) override { | |||
Widget::onHoverScroll(e); | |||
if (!e.getConsumed()) | |||
e.consume(this); | |||
} | |||
void onPathDrop(const PathDropEvent &e) override { | |||
Widget::onPathDrop(e); | |||
if (!e.getConsumed()) | |||
e.consume(this); | |||
} | |||
}; | |||
} // namespace widget | |||
} // namespace rack |
@@ -98,13 +98,18 @@ struct Widget { | |||
template <typename TMethod, class TEvent> | |||
void recurseEvent(TMethod f, const TEvent &e) { | |||
for (auto it = children.rbegin(); it != children.rend(); it++) { | |||
// Stop propagation if requested | |||
if (!e.isPropagating()) | |||
break; | |||
Widget *child = *it; | |||
// Filter child by visibility | |||
if (!child->visible) | |||
continue; | |||
// Clone event for (currently) no reason | |||
TEvent e2 = e; | |||
// Call child event handler | |||
(child->*f)(e); | |||
(child->*f)(e2); | |||
} | |||
} | |||
@@ -112,6 +117,9 @@ struct Widget { | |||
template <typename TMethod, class TEvent> | |||
void recursePositionEvent(TMethod f, const TEvent &e) { | |||
for (auto it = children.rbegin(); it != children.rend(); it++) { | |||
// Stop propagation if requested | |||
if (!e.isPropagating()) | |||
break; | |||
Widget *child = *it; | |||
// Filter child by visibility and position | |||
if (!child->visible) | |||
@@ -124,9 +132,6 @@ struct Widget { | |||
e2.pos = e.pos.minus(child->box.pos); | |||
// Call child event handler | |||
(child->*f)(e2); | |||
// Stop iterating if consumed | |||
if (e.getConsumed()) | |||
break; | |||
} | |||
} | |||
@@ -13,10 +13,8 @@ struct Widget; | |||
/** A per-event state shared and writable by all widgets that recursively handle an event. */ | |||
struct EventContext { | |||
/** The Widget that consumes the event. | |||
This stops propagation of the event if applicable. | |||
*/ | |||
Widget *consumed = NULL; | |||
Widget *target = NULL; | |||
bool propagating = true; | |||
}; | |||
@@ -24,12 +22,33 @@ struct EventContext { | |||
struct Event { | |||
EventContext *context = NULL; | |||
void consume(Widget *w) const { | |||
/** Prevents the Event from being handled by more Widgets. | |||
*/ | |||
void stopPropagating() const { | |||
if (context) | |||
context->consumed = w; | |||
context->propagating = false; | |||
} | |||
Widget *getConsumed() const { | |||
return context ? context->consumed : NULL; | |||
bool isPropagating() const { | |||
if (context) | |||
return context->propagating; | |||
return true; | |||
} | |||
/** Tells the event handler that a particular Widget consumed the event. | |||
You usually want to stop propagation as well, so call consume() instead. | |||
*/ | |||
void setTarget(Widget *w) const { | |||
if (context) | |||
context->target = w; | |||
} | |||
Widget *getTarget() const { | |||
if (context) | |||
return context->target; | |||
return NULL; | |||
} | |||
/** Sets the target Widget and stops propagation. */ | |||
void consume(Widget *w) const { | |||
setTarget(w); | |||
stopPropagating(); | |||
} | |||
}; | |||
@@ -27,7 +27,7 @@ struct BlankPanel : Widget { | |||
}; | |||
struct ModuleResizeHandle : Widget { | |||
struct ModuleResizeHandle : OpaqueWidget { | |||
bool right = false; | |||
float dragX; | |||
Rect originalBox; | |||
@@ -59,13 +59,13 @@ struct ModuleResizeHandle : Widget { | |||
const float minWidth = 3 * RACK_GRID_WIDTH; | |||
if (right) { | |||
newBox.size.x += deltaX; | |||
newBox.size.x = fmaxf(newBox.size.x, minWidth); | |||
newBox.size.x = roundf(newBox.size.x / RACK_GRID_WIDTH) * RACK_GRID_WIDTH; | |||
newBox.size.x = std::fmax(newBox.size.x, minWidth); | |||
newBox.size.x = std::round(newBox.size.x / RACK_GRID_WIDTH) * RACK_GRID_WIDTH; | |||
} | |||
else { | |||
newBox.size.x -= deltaX; | |||
newBox.size.x = fmaxf(newBox.size.x, minWidth); | |||
newBox.size.x = roundf(newBox.size.x / RACK_GRID_WIDTH) * RACK_GRID_WIDTH; | |||
newBox.size.x = std::fmax(newBox.size.x, minWidth); | |||
newBox.size.x = std::round(newBox.size.x / RACK_GRID_WIDTH) * RACK_GRID_WIDTH; | |||
newBox.pos.x = originalBox.pos.x + originalBox.size.x - newBox.size.x; | |||
} | |||
APP->scene->rack->requestModuleBox(m, newBox); | |||
@@ -1,6 +1,5 @@ | |||
#include "app/ModuleBrowser.hpp" | |||
#include "widget/OpaqueWidget.hpp" | |||
#include "widget/OverlayWidget.hpp" | |||
#include "widget/TransparentWidget.hpp" | |||
#include "widget/ZoomWidget.hpp" | |||
#include "ui/ScrollWidget.hpp" | |||
@@ -53,21 +52,22 @@ static float modelScore(plugin::Model *model, const std::string &search) { | |||
} | |||
struct BrowserOverlay : widget::OverlayWidget { | |||
struct BrowserOverlay : widget::OpaqueWidget { | |||
void step() override { | |||
box = parent->box.zeroPos(); | |||
// Only step if visible, since there are potentially thousands of descendants that don't need to be stepped. | |||
if (visible) | |||
OverlayWidget::step(); | |||
OpaqueWidget::step(); | |||
} | |||
void onButton(const widget::ButtonEvent &e) override { | |||
OverlayWidget::onButton(e); | |||
if (e.getConsumed() != this) | |||
OpaqueWidget::onButton(e); | |||
if (e.getTarget() != this) | |||
return; | |||
if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT) { | |||
hide(); | |||
e.consume(this); | |||
} | |||
} | |||
}; | |||
@@ -291,7 +291,7 @@ struct BrowserSearchField : ui::TextField { | |||
} | |||
} | |||
if (!e.getConsumed()) | |||
if (!e.getTarget()) | |||
ui::TextField::onSelectKey(e); | |||
} | |||
@@ -615,7 +615,7 @@ struct ModuleBrowser : widget::OpaqueWidget { | |||
inline void ModelBox::onButton(const widget::ButtonEvent &e) { | |||
OpaqueWidget::onButton(e); | |||
if (e.getConsumed() != this) | |||
if (e.getTarget() != this) | |||
return; | |||
if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT) { | |||
@@ -321,15 +321,20 @@ void ModuleWidget::onHover(const widget::HoverEvent &e) { | |||
void ModuleWidget::onButton(const widget::ButtonEvent &e) { | |||
widget::OpaqueWidget::onButton(e); | |||
if (e.getTarget() != this) | |||
return; | |||
if (e.getConsumed() == this) { | |||
if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) { | |||
createContextMenu(); | |||
} | |||
if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) { | |||
createContextMenu(); | |||
e.consume(this); | |||
} | |||
} | |||
void ModuleWidget::onHoverKey(const widget::HoverKeyEvent &e) { | |||
widget::OpaqueWidget::onHoverKey(e); | |||
if (e.getTarget() != this) | |||
return; | |||
if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) { | |||
switch (e.key) { | |||
case GLFW_KEY_I: { | |||
@@ -376,9 +381,6 @@ void ModuleWidget::onHoverKey(const widget::HoverKeyEvent &e) { | |||
} break; | |||
} | |||
} | |||
if (!e.getConsumed()) | |||
widget::OpaqueWidget::onHoverKey(e); | |||
} | |||
void ModuleWidget::onDragStart(const widget::DragStartEvent &e) { | |||
@@ -51,7 +51,7 @@ struct ParamField : ui::TextField { | |||
e.consume(this); | |||
} | |||
if (!e.getConsumed()) | |||
if (!e.getTarget()) | |||
TextField::onSelectKey(e); | |||
} | |||
}; | |||
@@ -140,6 +140,10 @@ void ParamWidget::draw(const DrawArgs &args) { | |||
} | |||
void ParamWidget::onButton(const widget::ButtonEvent &e) { | |||
OpaqueWidget::onButton(e); | |||
if (e.getTarget() != this) | |||
return; | |||
// Touch parameter | |||
if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT && (e.mods & WINDOW_MOD_MASK) == 0) { | |||
if (paramQuantity) { | |||
@@ -152,9 +156,6 @@ void ParamWidget::onButton(const widget::ButtonEvent &e) { | |||
createContextMenu(); | |||
e.consume(this); | |||
} | |||
if (!e.getConsumed()) | |||
OpaqueWidget::onButton(e); | |||
} | |||
void ParamWidget::onDoubleClick(const widget::DoubleClickEvent &e) { | |||
@@ -60,6 +60,8 @@ void PortWidget::draw(const DrawArgs &args) { | |||
} | |||
void PortWidget::onButton(const widget::ButtonEvent &e) { | |||
OpaqueWidget::onButton(e); | |||
if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) { | |||
CableWidget *cw = APP->scene->rack->getTopCable(this); | |||
if (cw) { | |||
@@ -72,7 +74,6 @@ void PortWidget::onButton(const widget::ButtonEvent &e) { | |||
delete cw; | |||
} | |||
} | |||
e.consume(this); | |||
} | |||
void PortWidget::onEnter(const widget::EnterEvent &e) { | |||
@@ -123,7 +123,7 @@ void RackWidget::onHover(const widget::HoverEvent &e) { | |||
void RackWidget::onHoverKey(const widget::HoverKeyEvent &e) { | |||
OpaqueWidget::onHoverKey(e); | |||
if (e.getConsumed() != this) | |||
if (e.getTarget() != this) | |||
return; | |||
if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) { | |||
@@ -132,6 +132,7 @@ void RackWidget::onHoverKey(const widget::HoverKeyEvent &e) { | |||
if ((e.mods & WINDOW_MOD_MASK) == WINDOW_MOD_CTRL) { | |||
pastePresetClipboardAction(); | |||
} | |||
e.consume(this); | |||
} break; | |||
} | |||
} | |||
@@ -144,10 +145,12 @@ void RackWidget::onDragHover(const widget::DragHoverEvent &e) { | |||
void RackWidget::onButton(const widget::ButtonEvent &e) { | |||
OpaqueWidget::onButton(e); | |||
if (e.getConsumed() == this) { | |||
if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) { | |||
APP->scene->moduleBrowser->show(); | |||
} | |||
if (e.getTarget() != this) | |||
return; | |||
if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) { | |||
APP->scene->moduleBrowser->show(); | |||
e.consume(this); | |||
} | |||
} | |||
@@ -72,6 +72,10 @@ void Scene::draw(const DrawArgs &args) { | |||
} | |||
void Scene::onHoverKey(const widget::HoverKeyEvent &e) { | |||
OpaqueWidget::onHoverKey(e); | |||
if (e.getTarget() != this) | |||
return; | |||
if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) { | |||
switch (e.key) { | |||
case GLFW_KEY_N: { | |||
@@ -127,12 +131,13 @@ void Scene::onHoverKey(const widget::HoverKeyEvent &e) { | |||
} | |||
} | |||
} | |||
if (!e.getConsumed()) | |||
OpaqueWidget::onHoverKey(e); | |||
} | |||
void Scene::onPathDrop(const widget::PathDropEvent &e) { | |||
OpaqueWidget::onPathDrop(e); | |||
if (e.getTarget() != this) | |||
return; | |||
if (e.paths.size() >= 1) { | |||
const std::string &path = e.paths[0]; | |||
if (string::filenameExtension(string::filename(path)) == "vcv") { | |||
@@ -140,9 +145,6 @@ void Scene::onPathDrop(const widget::PathDropEvent &e) { | |||
e.consume(this); | |||
} | |||
} | |||
if (!e.getConsumed()) | |||
OpaqueWidget::onPathDrop(e); | |||
} | |||
void Scene::runCheckVersion() { | |||
@@ -423,7 +423,7 @@ struct AccountEmailField : ui::TextField { | |||
e.consume(this); | |||
} | |||
if (!e.getConsumed()) | |||
if (!e.getTarget()) | |||
ui::TextField::onSelectKey(e); | |||
} | |||
}; | |||
@@ -437,7 +437,7 @@ struct AccountPasswordField : ui::PasswordField { | |||
e.consume(this); | |||
} | |||
if (!e.getConsumed()) | |||
if (!e.getTarget()) | |||
ui::PasswordField::onSelectKey(e); | |||
} | |||
}; | |||
@@ -78,7 +78,7 @@ void MenuItem::doAction() { | |||
// Consume event by default, but allow action to un-consume it to prevent the menu from being removed. | |||
eAction.consume(this); | |||
onAction(eAction); | |||
if (!cAction.consumed) | |||
if (!cAction.target) | |||
return; | |||
MenuOverlay *overlay = getAncestorOfType<MenuOverlay>(); | |||
@@ -19,16 +19,20 @@ void MenuOverlay::step() { | |||
void MenuOverlay::onButton(const widget::ButtonEvent &e) { | |||
widget::OpaqueWidget::onButton(e); | |||
if (e.getTarget() != this) | |||
return; | |||
if (e.getConsumed() == this && e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT) { | |||
if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT) { | |||
requestedDelete = true; | |||
} | |||
} | |||
void MenuOverlay::onHoverKey(const widget::HoverKeyEvent &e) { | |||
widget::OpaqueWidget::onHoverKey(e); | |||
if (e.getTarget() != this) | |||
return; | |||
if (e.getConsumed() == this && e.action == GLFW_PRESS && e.key == GLFW_KEY_ESCAPE) { | |||
if (e.action == GLFW_PRESS && e.key == GLFW_KEY_ESCAPE) { | |||
requestedDelete = true; | |||
} | |||
} | |||
@@ -70,7 +70,7 @@ void ScrollWidget::onHover(const widget::HoverEvent &e) { | |||
void ScrollWidget::onHoverScroll(const widget::HoverScrollEvent &e) { | |||
widget::Widget::onHoverScroll(e); | |||
if (e.getConsumed()) | |||
if (e.getTarget() != this) | |||
return; | |||
// Scroll only if the scrollbars are visible | |||
@@ -30,21 +30,23 @@ void TextField::draw(const DrawArgs &args) { | |||
nvgResetScissor(args.vg); | |||
} | |||
void TextField::onButton(const widget::ButtonEvent &e) { | |||
if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT) { | |||
cursor = selection = getTextPosition(e.pos); | |||
} | |||
widget::OpaqueWidget::onButton(e); | |||
} | |||
void TextField::onHover(const widget::HoverEvent &e) { | |||
widget::OpaqueWidget::onHover(e); | |||
if (this == APP->event->draggedWidget) { | |||
int pos = getTextPosition(e.pos); | |||
if (pos != selection) { | |||
cursor = pos; | |||
} | |||
} | |||
widget::OpaqueWidget::onHover(e); | |||
} | |||
void TextField::onButton(const widget::ButtonEvent &e) { | |||
widget::OpaqueWidget::onButton(e); | |||
if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT) { | |||
cursor = selection = getTextPosition(e.pos); | |||
} | |||
} | |||
void TextField::onEnter(const widget::EnterEvent &e) { | |||
@@ -23,7 +23,7 @@ void EventState::setHovered(Widget *w) { | |||
EnterEvent eEnter; | |||
eEnter.context = &cEnter; | |||
w->onEnter(eEnter); | |||
hoveredWidget = cEnter.consumed; | |||
hoveredWidget = cEnter.target; | |||
} | |||
} | |||
@@ -44,7 +44,7 @@ void EventState::setDragged(Widget *w) { | |||
DragStartEvent eDragStart; | |||
eDragStart.context = &cDragStart; | |||
w->onDragStart(eDragStart); | |||
draggedWidget = cDragStart.consumed; | |||
draggedWidget = cDragStart.target; | |||
} | |||
} | |||
@@ -67,7 +67,7 @@ void EventState::setDragHovered(Widget *w) { | |||
eDragEnter.context = &cDragEnter; | |||
eDragEnter.origin = draggedWidget; | |||
w->onDragEnter(eDragEnter); | |||
dragHoveredWidget = cDragEnter.consumed; | |||
dragHoveredWidget = cDragEnter.target; | |||
} | |||
} | |||
@@ -88,7 +88,7 @@ void EventState::setSelected(Widget *w) { | |||
SelectEvent eSelect; | |||
eSelect.context = &cSelect; | |||
w->onSelect(eSelect); | |||
selectedWidget = cSelect.consumed; | |||
selectedWidget = cSelect.target; | |||
} | |||
} | |||
@@ -110,7 +110,7 @@ void EventState::handleButton(math::Vec pos, int button, int action, int mods) { | |||
eButton.action = action; | |||
eButton.mods = mods; | |||
rootWidget->onButton(eButton); | |||
Widget *clickedWidget = cButton.consumed; | |||
Widget *clickedWidget = cButton.target; | |||
if (button == GLFW_MOUSE_BUTTON_LEFT) { | |||
if (action == GLFW_PRESS) { | |||
@@ -171,7 +171,7 @@ void EventState::handleHover(math::Vec pos, math::Vec mouseDelta) { | |||
eDragHover.origin = draggedWidget; | |||
rootWidget->onDragHover(eDragHover); | |||
setDragHovered(cDragHover.consumed); | |||
setDragHovered(cDragHover.target); | |||
return; | |||
} | |||
@@ -184,7 +184,7 @@ void EventState::handleHover(math::Vec pos, math::Vec mouseDelta) { | |||
eHover.mouseDelta = mouseDelta; | |||
rootWidget->onHover(eHover); | |||
setHovered(cHover.consumed); | |||
setHovered(cHover.target); | |||
} | |||
void EventState::handleLeave() { | |||
@@ -219,7 +219,7 @@ void EventState::handleText(math::Vec pos, int codepoint) { | |||
eSelectText.context = &cSelectText; | |||
eSelectText.codepoint = codepoint; | |||
selectedWidget->onSelectText(eSelectText); | |||
if (cSelectText.consumed) | |||
if (cSelectText.target) | |||
return; | |||
} | |||
@@ -243,7 +243,7 @@ void EventState::handleKey(math::Vec pos, int key, int scancode, int action, int | |||
eSelectKey.action = action; | |||
eSelectKey.mods = mods; | |||
selectedWidget->onSelectKey(eSelectKey); | |||
if (cSelectKey.consumed) | |||
if (cSelectKey.target) | |||
return; | |||
} | |||