@@ -111,6 +111,7 @@ struct ModuleWidget : widget::OpaqueWidget { | |||||
INTERNAL bool& dragEnabled(); | INTERNAL bool& dragEnabled(); | ||||
INTERNAL math::Vec& oldPos(); | INTERNAL math::Vec& oldPos(); | ||||
INTERNAL engine::Module* releaseModule(); | INTERNAL engine::Module* releaseModule(); | ||||
INTERNAL bool& selected(); | |||||
}; | }; | ||||
@@ -38,8 +38,12 @@ struct RackWidget : widget::OpaqueWidget { | |||||
void onHover(const HoverEvent& e) override; | void onHover(const HoverEvent& e) override; | ||||
void onHoverKey(const HoverKeyEvent& e) override; | void onHoverKey(const HoverKeyEvent& e) override; | ||||
void onDragHover(const DragHoverEvent& e) override; | |||||
void onButton(const ButtonEvent& e) override; | void onButton(const ButtonEvent& e) override; | ||||
void onDragStart(const DragStartEvent& e) override; | |||||
void onDragEnd(const DragEndEvent& e) override; | |||||
void onDragHover(const DragHoverEvent& e) override; | |||||
// Rack methods | |||||
/** Completely clear the rack's modules and cables */ | /** Completely clear the rack's modules and cables */ | ||||
void clear(); | void clear(); | ||||
@@ -65,6 +69,7 @@ struct RackWidget : widget::OpaqueWidget { | |||||
bool isEmpty(); | bool isEmpty(); | ||||
void updateModuleOldPositions(); | void updateModuleOldPositions(); | ||||
history::ComplexAction* getModuleDragAction(); | history::ComplexAction* getModuleDragAction(); | ||||
void updateModuleSelections(); | |||||
// Cable methods | // Cable methods | ||||
@@ -308,10 +308,16 @@ struct Rect { | |||||
Rect() {} | Rect() {} | ||||
Rect(Vec pos, Vec size) : pos(pos), size(size) {} | Rect(Vec pos, Vec size) : pos(pos), size(size) {} | ||||
Rect(float posX, float posY, float sizeX, float sizeY) : pos(Vec(posX, posY)), size(Vec(sizeX, sizeY)) {} | Rect(float posX, float posY, float sizeX, float sizeY) : pos(Vec(posX, posY)), size(Vec(sizeX, sizeY)) {} | ||||
/** Constructs a Rect from the upper-left position `a` and lower-right pos `b`. */ | |||||
/** Constructs a Rect from a top-left and bottom-right vector. | |||||
*/ | |||||
static Rect fromMinMax(Vec a, Vec b) { | static Rect fromMinMax(Vec a, Vec b) { | ||||
return Rect(a, b.minus(a)); | return Rect(a, b.minus(a)); | ||||
} | } | ||||
/** Constructs a Rect from any two opposite corners. | |||||
*/ | |||||
static Rect fromCorners(Vec a, Vec b) { | |||||
return fromMinMax(a.min(b), a.max(b)); | |||||
} | |||||
/** Returns the infinite Rect. */ | /** Returns the infinite Rect. */ | ||||
static Rect inf() { | static Rect inf() { | ||||
return Rect(Vec(-INFINITY, -INFINITY), Vec(INFINITY, INFINITY)); | return Rect(Vec(-INFINITY, -INFINITY), Vec(INFINITY, INFINITY)); | ||||
@@ -418,6 +418,8 @@ struct ModuleWidget::Internal { | |||||
math::Vec oldPos; | math::Vec oldPos; | ||||
widget::Widget* panel = NULL; | widget::Widget* panel = NULL; | ||||
bool selected = false; | |||||
}; | }; | ||||
@@ -597,6 +599,17 @@ void ModuleWidget::draw(const DrawArgs& args) { | |||||
// bndLabel(args.vg, 0, 0, INFINITY, INFINITY, -1, debugText.c_str()); | // bndLabel(args.vg, 0, 0, INFINITY, INFINITY, -1, debugText.c_str()); | ||||
// } | // } | ||||
// Selection | |||||
if (internal->selected) { | |||||
nvgBeginPath(args.vg); | |||||
nvgRect(args.vg, 0.0, 0.0, VEC_ARGS(box.size)); | |||||
nvgFillColor(args.vg, nvgRGBAf(1, 0, 0, 0.25)); | |||||
nvgFill(args.vg); | |||||
nvgStrokeWidth(args.vg, 2.0); | |||||
nvgStrokeColor(args.vg, nvgRGBAf(1, 0, 0, 0.5)); | |||||
nvgStroke(args.vg); | |||||
} | |||||
nvgResetScissor(args.vg); | nvgResetScissor(args.vg); | ||||
} | } | ||||
@@ -1210,5 +1223,11 @@ engine::Module* ModuleWidget::releaseModule() { | |||||
} | } | ||||
bool& ModuleWidget::selected() { | |||||
return internal->selected; | |||||
} | |||||
} // namespace app | } // namespace app | ||||
} // namespace rack | } // namespace rack |
@@ -72,12 +72,15 @@ struct CableContainer : widget::TransparentWidget { | |||||
}; | }; | ||||
// struct RackWidget::Internal { | |||||
// }; | |||||
struct RackWidget::Internal { | |||||
bool selecting = false; | |||||
math::Vec selectionStart; | |||||
math::Vec selectionEnd; | |||||
}; | |||||
RackWidget::RackWidget() { | RackWidget::RackWidget() { | ||||
// internal = new Internal; | |||||
internal = new Internal; | |||||
rail = new RailWidget; | rail = new RailWidget; | ||||
addChild(rail); | addChild(rail); | ||||
@@ -91,7 +94,7 @@ RackWidget::RackWidget() { | |||||
RackWidget::~RackWidget() { | RackWidget::~RackWidget() { | ||||
clear(); | clear(); | ||||
// delete internal; | |||||
delete internal; | |||||
} | } | ||||
void RackWidget::step() { | void RackWidget::step() { | ||||
@@ -104,11 +107,24 @@ void RackWidget::draw(const DrawArgs& args) { | |||||
nvgGlobalTint(args.vg, nvgRGBAf(b, b, b, 1)); | nvgGlobalTint(args.vg, nvgRGBAf(b, b, b, 1)); | ||||
Widget::draw(args); | Widget::draw(args); | ||||
// Draw selection rectangle | |||||
if (internal->selecting) { | |||||
nvgBeginPath(args.vg); | |||||
math::Rect selectionBox = math::Rect::fromCorners(internal->selectionStart, internal->selectionEnd); | |||||
nvgRect(args.vg, RECT_ARGS(selectionBox)); | |||||
nvgFillColor(args.vg, nvgRGBAf(1, 0, 0, 0.25)); | |||||
nvgFill(args.vg); | |||||
nvgStrokeWidth(args.vg, 2.0); | |||||
nvgStrokeColor(args.vg, nvgRGBAf(1, 0, 0, 0.5)); | |||||
nvgStroke(args.vg); | |||||
} | |||||
} | } | ||||
void RackWidget::onHover(const HoverEvent& e) { | void RackWidget::onHover(const HoverEvent& e) { | ||||
// Set before calling children's onHover() | // Set before calling children's onHover() | ||||
mousePos = e.pos; | mousePos = e.pos; | ||||
OpaqueWidget::onHover(e); | OpaqueWidget::onHover(e); | ||||
} | } | ||||
@@ -125,12 +141,6 @@ void RackWidget::onHoverKey(const HoverKeyEvent& e) { | |||||
} | } | ||||
} | } | ||||
void RackWidget::onDragHover(const DragHoverEvent& e) { | |||||
// Set before calling children's onDragHover() | |||||
mousePos = e.pos; | |||||
OpaqueWidget::onDragHover(e); | |||||
} | |||||
void RackWidget::onButton(const ButtonEvent& e) { | void RackWidget::onButton(const ButtonEvent& e) { | ||||
Widget::onButton(e); | Widget::onButton(e); | ||||
e.stopPropagating(); | e.stopPropagating(); | ||||
@@ -141,6 +151,37 @@ void RackWidget::onButton(const ButtonEvent& e) { | |||||
APP->scene->moduleBrowser->show(); | APP->scene->moduleBrowser->show(); | ||||
e.consume(this); | e.consume(this); | ||||
} | } | ||||
if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT) { | |||||
e.consume(this); | |||||
} | |||||
} | |||||
void RackWidget::onDragStart(const DragStartEvent& e) { | |||||
if (e.button == GLFW_MOUSE_BUTTON_LEFT) { | |||||
// Deselect all modules | |||||
updateModuleSelections(); | |||||
internal->selecting = true; | |||||
internal->selectionStart = mousePos; | |||||
internal->selectionEnd = mousePos; | |||||
} | |||||
} | |||||
void RackWidget::onDragEnd(const DragEndEvent& e) { | |||||
if (e.button == GLFW_MOUSE_BUTTON_LEFT) { | |||||
internal->selecting = false; | |||||
} | |||||
} | |||||
void RackWidget::onDragHover(const DragHoverEvent& e) { | |||||
// Set before calling children's onDragHover() | |||||
mousePos = e.pos; | |||||
if (internal->selecting) { | |||||
internal->selectionEnd = mousePos; | |||||
updateModuleSelections(); | |||||
} | |||||
OpaqueWidget::onDragHover(e); | |||||
} | } | ||||
void RackWidget::clear() { | void RackWidget::clear() { | ||||
@@ -590,6 +631,15 @@ history::ComplexAction* RackWidget::getModuleDragAction() { | |||||
return h; | return h; | ||||
} | } | ||||
void RackWidget::updateModuleSelections() { | |||||
math::Rect selectionBox = math::Rect::fromCorners(internal->selectionStart, internal->selectionEnd); | |||||
for (widget::Widget* w : moduleContainer->children) { | |||||
ModuleWidget* mw = dynamic_cast<ModuleWidget*>(w); | |||||
assert(mw); | |||||
bool selected = internal->selecting && selectionBox.intersects(mw->box); | |||||
mw->selected() = selected; | |||||
} | |||||
} | |||||
void RackWidget::clearCables() { | void RackWidget::clearCables() { | ||||
incompleteCable = NULL; | incompleteCable = NULL; | ||||