a whole lotta work!!
- refactoring - completely changed the tooling (adding elements) - better re-use for more stable editing - new elements - ui adjustments - LINT for stair-editing - many more changes
This commit is contained in:
304
mapview/2D/tools/ToolSelector.h
Normal file
304
mapview/2D/tools/ToolSelector.h
Normal file
@@ -0,0 +1,304 @@
|
||||
#ifndef TOOLSELECTOR_H
|
||||
#define TOOLSELECTOR_H
|
||||
|
||||
|
||||
#include "Tool.h"
|
||||
#include "../MapView2D.h"
|
||||
|
||||
#include "../../model/MapModelElement.h"
|
||||
#include "../../model/MapModel.h"
|
||||
|
||||
|
||||
/**
|
||||
* this tool allows:
|
||||
* - selecting elements within the 2D view (focus/unfocus)
|
||||
* - selecting and moving nodes of elements inheriting from HasMoveableNodes
|
||||
*
|
||||
*/
|
||||
class ToolSelector : public Tool {
|
||||
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
|
||||
/** the currently focused MapElement (if any) */
|
||||
MapModelElement* focused = nullptr;
|
||||
|
||||
/** whether the mouse-button is currently pressed */
|
||||
bool mouseIsDown = false;
|
||||
|
||||
public:
|
||||
|
||||
virtual ~ToolSelector() {;}
|
||||
|
||||
|
||||
const std::string getName() const override {
|
||||
return "Selection";
|
||||
}
|
||||
|
||||
void becomesActive() override {
|
||||
showHelp();
|
||||
}
|
||||
|
||||
/** needed eg. when the focused element gets invalid (switching visible layers) */
|
||||
void layerChange(MapView2D* m) override {
|
||||
(void) m;
|
||||
setFocused(m, nullptr);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
void processUnfocus(MapView2D* m, MapModelElement* elem) {
|
||||
|
||||
MV2DElement* me = elem->getMV2D();
|
||||
if (!me) {return;}
|
||||
|
||||
// elements has selectedable nodes? unselect them
|
||||
if (dynamic_cast<HasMoveableNodes*>(me)) {
|
||||
dynamic_cast<HasMoveableNodes*>(me)->onNodeUnselect(m);
|
||||
}
|
||||
|
||||
// let the element itself process the unfocus
|
||||
me->unfocus();
|
||||
|
||||
}
|
||||
|
||||
void processPress(MapView2D* m, const Point2 p, MapModelElement* elem) {
|
||||
|
||||
MV2DElement* me = elem->getMV2D();
|
||||
if (!me) {return;}
|
||||
|
||||
// let the element itself process all events
|
||||
me->mousePressed(m, p);
|
||||
|
||||
}
|
||||
|
||||
void processMove(MapView2D* m, const Point2 p, MapModelElement* elem) {
|
||||
|
||||
MV2DElement* me = elem->getMV2D();
|
||||
if (!me) {return;}
|
||||
|
||||
// elements has selectedable nodes? try to process them
|
||||
if (dynamic_cast<HasMoveableNodes*>(me)) {
|
||||
if (moveNode(m, p, dynamic_cast<HasMoveableNodes*>(me))) {return;}
|
||||
}
|
||||
|
||||
// otherwise: let the element itself process all events
|
||||
me->mouseMove(m, p);
|
||||
|
||||
}
|
||||
|
||||
void processRelease(MapView2D* m, const Point2 p, MapModelElement* elem) {
|
||||
|
||||
MV2DElement* me = elem->getMV2D();
|
||||
if (!me) {return;}
|
||||
|
||||
// element has selectedable nodes? try to process them
|
||||
if (dynamic_cast<HasMoveableNodes*>(me)) {
|
||||
if (selectNode(m, p, dynamic_cast<HasMoveableNodes*>(me))) {return;}
|
||||
}
|
||||
|
||||
// otherwise: let the element itself process all events
|
||||
me->mouseReleased(m, p);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
private:
|
||||
|
||||
/** the given element has selectable nodes. try to select the one near to the mouse. true if successful */
|
||||
bool selectNode(MapView2D* v, Point2 p, HasMoveableNodes* elem) {
|
||||
|
||||
// select a new point on mouse-release (more robust than on mouse-press)
|
||||
|
||||
// find the node nearest to p
|
||||
auto comp = [&] (const MoveableNode& n1, const MoveableNode& n2) {return n1.pos.getDistance(p) < n2.pos.getDistance(p);};
|
||||
auto lst = elem->getMoveableNodes();
|
||||
auto it = std::min_element(lst.begin(), lst.end(), comp);
|
||||
|
||||
// is the nearest point below the threshold?
|
||||
const float t = v->getScaler().sm(CFG::SEL_THRESHOLD_SIZE_PX);
|
||||
const float d = it->pos.getDistance(p);
|
||||
if (d < t) {
|
||||
elem->onNodeSelect(v, it->userIdx);
|
||||
return true;
|
||||
} else {
|
||||
elem->onNodeSelect(v, -1);
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** move the currently selected node. true if successful */
|
||||
bool moveNode(MapView2D* v, Point2 p, HasMoveableNodes* elem) {
|
||||
|
||||
// no node selected? -> done
|
||||
if (elem->getSelectedNode() == -1) {return false;}
|
||||
|
||||
// snap the node
|
||||
const Point2 pSnapped = v->getScaler().snap(p);
|
||||
|
||||
// move
|
||||
elem->onNodeMove(v, elem->getSelectedNode(), pSnapped);
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
private:
|
||||
|
||||
void showHelp() {
|
||||
if (focused) {emit onHelpTextChange("select one of the element's nodes");}
|
||||
if (!focused) {emit onHelpTextChange("select an element by clicking on it");}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief change the currently focused element. throws an event.
|
||||
* @param el the to-be-focused element or null
|
||||
* @return true if the focused element has changed. false otherwise
|
||||
*/
|
||||
bool setFocused(MapView2D* v, MapModelElement* el) {
|
||||
|
||||
// whether the focus has changed or not
|
||||
const bool focusChanged = (focused != el);
|
||||
|
||||
if (focusChanged) {
|
||||
|
||||
// unfocus the old one (if any)
|
||||
if (focused) {processUnfocus(v, focused);}
|
||||
|
||||
// set the currently focused object (if any)
|
||||
focused = el;
|
||||
|
||||
// focus the new one (if any)
|
||||
if (focused) {focused->getMV2D()->focus();}
|
||||
|
||||
// update the help-text
|
||||
showHelp();
|
||||
|
||||
emit onMapElementSelected(el);
|
||||
|
||||
}
|
||||
|
||||
// done
|
||||
return focusChanged;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
private:
|
||||
|
||||
virtual bool mousePressEvent(MapView2D* m, QMouseEvent* e) override {
|
||||
|
||||
if (e->button() != Qt::MouseButton::LeftButton) {return false;}
|
||||
|
||||
mouseIsDown = true;
|
||||
|
||||
const Scaler& s = m->getScaler();
|
||||
const Point2 p2(s.xsm(e->x()), s.ysm(e->y()));
|
||||
|
||||
const float g = m->getScaler().sm(15); // increase each BBox by 15 px (needed mainly for hor/ver lines)
|
||||
|
||||
// get all elements with bounding-box matchings
|
||||
std::vector<MapModelElement*> possible;
|
||||
for (MapModelElement* el : m->getModel()->getSelectedLayerElements()) {
|
||||
if (!el->getMV2D()) {continue;}
|
||||
BBox2 bbox = el->getMV2D()->getBoundingBox(); // elements 2D bbox
|
||||
bbox.grow(Point2(g, g)); // grow a little (needed for straight lines)
|
||||
if (bbox.contains(p2)) {possible.push_back(el);} // intersection?
|
||||
}
|
||||
|
||||
// among those, find the best-matching one (smallest distance to an intersection)
|
||||
auto lambda = [&] (const MapModelElement* el1, const MapModelElement* el2) {return el1->getMV2D()->getMinDistanceXY(p2) < el2->getMV2D()->getMinDistanceXY(p2);};
|
||||
auto it = std::min_element(possible.begin(), possible.end(), lambda);
|
||||
MapModelElement* el = (it == possible.end()) ? (nullptr) : (*it);
|
||||
|
||||
|
||||
|
||||
// focus changed? -> unfocus the old one (if any)
|
||||
if (setFocused(m, el)) {
|
||||
|
||||
;
|
||||
|
||||
} else {
|
||||
|
||||
// focus kept. provide the currently focused element with events
|
||||
if (focused) {
|
||||
processPress(m, p2, focused);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
virtual bool mouseMoveEvent(MapView2D* m, QMouseEvent* e) override {
|
||||
|
||||
const Scaler& s = m->getScaler();
|
||||
Point2 p2(s.xsm(e->x()), s.ysm(e->y()));
|
||||
|
||||
// mouse pressed?
|
||||
if (mouseIsDown) {
|
||||
|
||||
// provide the focused element with the mouse event?
|
||||
if (focused) {processMove(m, p2, focused);}
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
virtual bool mouseReleaseEvent(MapView2D* m, QMouseEvent* e) override {
|
||||
|
||||
const Scaler& s = m->getScaler();
|
||||
Point2 p2(s.xsm(e->x()), s.ysm(e->y()));
|
||||
|
||||
// provide the focused element with the mouse event?
|
||||
if (focused) {processRelease(m, p2, focused);}
|
||||
|
||||
mouseIsDown = false;
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
virtual bool keyPressEvent(MapView2D* m, QKeyEvent* e) override {
|
||||
|
||||
(void) m;
|
||||
|
||||
if (focused) {
|
||||
|
||||
// pass it to the element which may consume this event
|
||||
if (focused->getMV2D()->keyPressEvent(m, e)) {return false;}
|
||||
|
||||
// not consumed -> additional options
|
||||
|
||||
// ESCAPE -> unfocus
|
||||
if (e->key() == Qt::Key_Escape) {setFocused(m, nullptr);}
|
||||
|
||||
// DELETE -> delete
|
||||
if (e->key() == Qt::Key_Delete) {
|
||||
focused->deleteMe();
|
||||
setFocused(m, nullptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
signals:
|
||||
|
||||
/** map element was selected */
|
||||
void onMapElementSelected(MapModelElement* el);
|
||||
|
||||
};
|
||||
|
||||
#endif // TOOLSELECTOR_H
|
||||
Reference in New Issue
Block a user