diff options
author | Sergiusz Bazanski <q3k@q3k.org> | 2018-07-26 22:40:45 +0100 |
---|---|---|
committer | Sergiusz Bazanski <q3k@q3k.org> | 2018-07-26 22:40:45 +0100 |
commit | df908374dc233c23aef0790cbce65aa0a58c81ec (patch) | |
tree | d467c0c385f967e1bf2b030d34c62492228fda4d /gui | |
parent | 567566585ce9c4e15bd72f914084a92f7a62d553 (diff) | |
download | nextpnr-df908374dc233c23aef0790cbce65aa0a58c81ec.tar.gz nextpnr-df908374dc233c23aef0790cbce65aa0a58c81ec.tar.bz2 nextpnr-df908374dc233c23aef0790cbce65aa0a58c81ec.zip |
gui: implement basic cursor picking
Diffstat (limited to 'gui')
-rw-r--r-- | gui/basewindow.cc | 1 | ||||
-rw-r--r-- | gui/designwidget.cc | 7 | ||||
-rw-r--r-- | gui/designwidget.h | 4 | ||||
-rw-r--r-- | gui/fpgaviewwidget.cc | 267 | ||||
-rw-r--r-- | gui/fpgaviewwidget.h | 58 |
5 files changed, 285 insertions, 52 deletions
diff --git a/gui/basewindow.cc b/gui/basewindow.cc index cc6ef4a5..c7e637f6 100644 --- a/gui/basewindow.cc +++ b/gui/basewindow.cc @@ -83,6 +83,7 @@ BaseMainWindow::BaseMainWindow(std::unique_ptr<Context> context, QWidget *parent connect(designview, SIGNAL(selected(std::vector<DecalXY>)), fpgaView,
SLOT(onSelectedArchItem(std::vector<DecalXY>)));
connect(fpgaView, SIGNAL(clickedBel(BelId)), designview, SLOT(onClickedBel(BelId)));
+ connect(fpgaView, SIGNAL(clickedWire(WireId)), designview, SLOT(onClickedWire(WireId)));
connect(designview, SIGNAL(highlight(std::vector<DecalXY>, int)), fpgaView,
SLOT(onHighlightGroupChanged(std::vector<DecalXY>, int)));
diff --git a/gui/designwidget.cc b/gui/designwidget.cc index 989b55fe..89babda2 100644 --- a/gui/designwidget.cc +++ b/gui/designwidget.cc @@ -514,6 +514,13 @@ void DesignWidget::onClickedBel(BelId bel) Q_EMIT selected(getDecals(ElementType::BEL, ctx->getBelName(bel)));
}
+void DesignWidget::onClickedWire(WireId wire)
+{
+ QTreeWidgetItem *item = nameToItem[getElementIndex(ElementType::WIRE)].value(ctx->getWireName(wire).c_str(ctx));
+ treeWidget->setCurrentItem(item);
+ Q_EMIT selected(getDecals(ElementType::WIRE, ctx->getWireName(wire)));
+}
+
void DesignWidget::onItemSelectionChanged()
{
if (treeWidget->selectedItems().size() == 0)
diff --git a/gui/designwidget.h b/gui/designwidget.h index 85c326d0..fec0d069 100644 --- a/gui/designwidget.h +++ b/gui/designwidget.h @@ -37,7 +37,8 @@ enum class ElementType WIRE,
PIP,
NET,
- CELL
+ CELL,
+ GROUP
};
class DesignWidget : public QWidget
@@ -75,6 +76,7 @@ class DesignWidget : public QWidget void newContext(Context *ctx);
void updateTree();
void onClickedBel(BelId bel);
+ void onClickedWire(WireId wire);
private:
Context *ctx;
diff --git a/gui/fpgaviewwidget.cc b/gui/fpgaviewwidget.cc index fef6c328..46008ece 100644 --- a/gui/fpgaviewwidget.cc +++ b/gui/fpgaviewwidget.cc @@ -44,6 +44,7 @@ FPGAViewWidget::FPGAViewWidget(QWidget *parent) : colors_.inactive = QColor("#303030"); colors_.active = QColor("#f0f0f0"); colors_.selected = QColor("#ff6600"); + colors_.hovered = QColor("#906030"); colors_.highlight[0] = QColor("#6495ed"); colors_.highlight[1] = QColor("#7fffd4"); colors_.highlight[2] = QColor("#98fb98"); @@ -53,7 +54,7 @@ FPGAViewWidget::FPGAViewWidget(QWidget *parent) : colors_.highlight[6] = QColor("#ff69b4"); colors_.highlight[7] = QColor("#da70d6"); - rendererArgs_->highlightedOrSelectedChanged = false; + rendererArgs_->changed = false; auto fmt = format(); fmt.setMajorVersion(3); @@ -75,6 +76,7 @@ FPGAViewWidget::FPGAViewWidget(QWidget *parent) : renderRunner_ = std::unique_ptr<PeriodicRunner>(new PeriodicRunner(this, [this] { renderLines(); })); renderRunner_->start(); renderRunner_->startTimer(1000 / 2); // render lines 2 times per second + setMouseTracking(true); } FPGAViewWidget::~FPGAViewWidget() {} @@ -102,6 +104,68 @@ void FPGAViewWidget::initializeGL() 0.0); } +float FPGAViewWidget::PickedElement::distance(Context *ctx, float wx, float wy) const +{ + // Get DecalXY for this element. + DecalXY dec = decal(ctx); + + // Coordinates within decal. + float dx = wx - dec.x; + float dy = wy - dec.y; + + auto graphics = ctx->getDecalGraphics(dec.decal); + if (graphics.size() == 0) + return -1; + + // TODO(q3k): For multi-line decals, find intersections and also calculate distance to them. + + // Go over its' GraphicElements, and calculate the distance to them. + std::vector<float> distances; + std::transform(graphics.begin(), graphics.end(), std::back_inserter(distances), [&](const GraphicElement &ge) -> float { + switch(ge.type) { + case GraphicElement::TYPE_BOX: + { + // If outside the box, return unit distance to closest border. + float outside_x = -1, outside_y = -1; + if (dx < ge.x1 || dx > ge.x2) { + outside_x = std::min(std::abs(dx - ge.x1), std::abs(dx - ge.x2)); + } + if (dy < ge.y1 || dy > ge.y2) { + outside_y = std::min(std::abs(dy - ge.y1), std::abs(dy - ge.y2)); + } + if (outside_x != -1 && outside_y != -1) + return std::min(outside_x, outside_y); + + // If in box, return 0. + return 0; + } + case GraphicElement::TYPE_LINE: + case GraphicElement::TYPE_ARROW: + { + // Return somewhat primitively calculated distance to segment. + // TODO(q3k): consider coming up with a better algorithm + QVector2D w(wx, wy); + QVector2D a(ge.x1, ge.y1); + QVector2D b(ge.x2, ge.y2); + float dw = a.distanceToPoint(w) + b.distanceToPoint(w); + float dab = a.distanceToPoint(b); + return std::abs(dw-dab) / dab; + } + default: + // Not close to antyhing. + return -1; + } + }); + + // Find smallest non -1 distance. + // Find closest element. + return *std::min_element(distances.begin(), distances.end(), [&](float a, float b) { + if (a == -1) return false; + if (b == -1) return true; + return a < b; + }); +} + void FPGAViewWidget::renderGraphicElement(RendererData *data, LineShaderData &out, const GraphicElement &el, float x, float y) { if (el.type == GraphicElement::TYPE_BOX) { @@ -153,8 +217,40 @@ void FPGAViewWidget::renderArchDecal(RendererData *data, const DecalXY &decal) } } -void FPGAViewWidget::populateQuadTree(RendererData *data, const DecalXY &decal, IdString id) +void FPGAViewWidget::populateQuadTree(RendererData *data, const DecalXY &decal, const PickedElement &element) { + float x = decal.x; + float y = decal.y; + + for (auto &el : ctx_->getDecalGraphics(decal.decal)) { + if (el.style == GraphicElement::STYLE_HIDDEN) { + continue; + } + + if (el.type == GraphicElement::TYPE_BOX) { + // Boxes are bounded by themselves. + data->qt->insert(PickQuadTree::BoundingBox(x+el.x1, y+el.y1, x+el.x2, y+el.y2), element); + } + + if (el.type == GraphicElement::TYPE_LINE || el.type == GraphicElement::TYPE_ARROW) { + // Lines are bounded by their AABB slightly enlarged. + float x0 = x+el.x1; + float y0 = y+el.y1; + float x1 = x+el.x2; + float y1 = y+el.y2; + if (x1 < x0) + std::swap(x0, x1); + if (y1 < y0) + std::swap(y0, y1); + + x0 -= 0.01; + y0 -= 0.01; + x1 += 0.01; + y1 += 0.01; + + data->qt->insert(PickQuadTree::BoundingBox(x0, y0, x1, y1), element); + } + } } QMatrix4x4 FPGAViewWidget::getProjection(void) @@ -181,6 +277,7 @@ void FPGAViewWidget::paintGL() // Calculate world thickness to achieve a screen 1px/1.1px line. float thick1Px = mouseToWorldDimensions(1, 0).x(); float thick11Px = mouseToWorldDimensions(1.1, 0).x(); + float thick2Px = mouseToWorldDimensions(2, 0).x(); // Render grid. auto grid = LineShaderData(); @@ -205,6 +302,7 @@ void FPGAViewWidget::paintGL() lineShader_.draw(rendererData_->gfxHighlighted[i], colors_.highlight[i], thick11Px, matrix); lineShader_.draw(rendererData_->gfxSelected, colors_.selected, thick11Px, matrix); + lineShader_.draw(rendererData_->gfxHovered, colors_.hovered, thick2Px, matrix); } } @@ -216,10 +314,10 @@ void FPGAViewWidget::renderLines(void) return; // Data from Context needed to render all decals. - std::vector<std::pair<DecalXY, IdString>> belDecals; - std::vector<std::pair<DecalXY, IdString>> wireDecals; - std::vector<std::pair<DecalXY, IdString>> pipDecals; - std::vector<std::pair<DecalXY, IdString>> groupDecals; + std::vector<std::pair<DecalXY, BelId>> belDecals; + std::vector<std::pair<DecalXY, WireId>> wireDecals; + std::vector<std::pair<DecalXY, PipId>> pipDecals; + std::vector<std::pair<DecalXY, GroupId>> groupDecals; bool decalsChanged = false; { // Take the UI/Normal mutex on the Context, copy over all we need as @@ -257,32 +355,34 @@ void FPGAViewWidget::renderLines(void) // Local copy of decals, taken as fast as possible to not block the P&R. if (decalsChanged) { for (auto bel : ctx_->getBels()) { - belDecals.push_back({ctx_->getBelDecal(bel), ctx_->getBelName(bel)}); + belDecals.push_back({ctx_->getBelDecal(bel), bel}); } for (auto wire : ctx_->getWires()) { - wireDecals.push_back({ctx_->getWireDecal(wire), ctx_->getWireName(wire)}); + wireDecals.push_back({ctx_->getWireDecal(wire), wire}); } for (auto pip : ctx_->getPips()) { - pipDecals.push_back({ctx_->getPipDecal(pip), ctx_->getPipName(pip)}); + pipDecals.push_back({ctx_->getPipDecal(pip), pip}); } for (auto group : ctx_->getGroups()) { - groupDecals.push_back({ctx_->getGroupDecal(group), ctx_->getGroupName(group)}); + groupDecals.push_back({ctx_->getGroupDecal(group), group}); } } } // Arguments from the main UI thread on what we should render. std::vector<DecalXY> selectedDecals; + DecalXY hoveredDecal; std::vector<DecalXY> highlightedDecals[8]; bool highlightedOrSelectedChanged; { // Take the renderer arguments lock, copy over all we need. QMutexLocker lock(&rendererArgsLock_); selectedDecals = rendererArgs_->selectedDecals; + hoveredDecal = rendererArgs_->hoveredDecal; for (int i = 0; i < 8; i++) highlightedDecals[i] = rendererArgs_->highlightedDecals[i]; - highlightedOrSelectedChanged = rendererArgs_->highlightedOrSelectedChanged; - rendererArgs_->highlightedOrSelectedChanged = false; + highlightedOrSelectedChanged = rendererArgs_->changed; + rendererArgs_->changed = false; } @@ -315,14 +415,22 @@ void FPGAViewWidget::renderLines(void) // Bounding box should be calculated by now. NPNR_ASSERT((data->bbX1 - data->bbX0) != 0); NPNR_ASSERT((data->bbY1 - data->bbY0) != 0); - auto bb = QuadTreeElements::BoundingBox(data->bbX0, data->bbY0, data->bbX1, data->bbY1); + auto bb = PickQuadTree::BoundingBox(data->bbX0, data->bbY0, data->bbX1, data->bbY1); // Populate picking quadtree. - //data->qt = std::unique_ptr<QuadTreeElements>(new QuadTreeElements(bb)); - - //for (auto const &decal : belDecals) { - // populateQuadTree(data.get(), decal.first, decal.second); - //} + data->qt = std::unique_ptr<PickQuadTree>(new PickQuadTree(bb)); + for (auto const &decal : belDecals) { + populateQuadTree(data.get(), decal.first, PickedElement(decal.second, decal.first.x, decal.first.y)); + } + for (auto const &decal : wireDecals) { + populateQuadTree(data.get(), decal.first, PickedElement(decal.second, decal.first.x, decal.first.y)); + } + for (auto const &decal : pipDecals) { + populateQuadTree(data.get(), decal.first, PickedElement(decal.second, decal.first.x, decal.first.y)); + } + for (auto const &decal : groupDecals) { + populateQuadTree(data.get(), decal.first, PickedElement(decal.second, decal.first.x, decal.first.y)); + } // Swap over. { @@ -332,6 +440,7 @@ void FPGAViewWidget::renderLines(void) // copy them over from teh current object. if (!highlightedOrSelectedChanged) { data->gfxSelected = rendererData_->gfxSelected; + data->gfxHovered = rendererData_->gfxHovered; for (int i = 0; i < 8; i++) data->gfxHighlighted[i] = rendererData_->gfxHighlighted[i]; } @@ -343,12 +452,22 @@ void FPGAViewWidget::renderLines(void) if (highlightedOrSelectedChanged) { QMutexLocker locker(&rendererDataLock_); + // Whether the currently being hovered decal is also selected. + bool hoveringSelected = false; // Render selected. rendererData_->gfxSelected.clear(); for (auto &decal : selectedDecals) { + if (decal == hoveredDecal) + hoveringSelected = true; renderDecal(rendererData_.get(), rendererData_->gfxSelected, decal); } + // Render hovered. + rendererData_->gfxHovered.clear(); + if (!hoveringSelected) { + renderDecal(rendererData_.get(), rendererData_->gfxHovered, hoveredDecal); + } + // Render highlighted. for (int i = 0; i < 8; i++) { rendererData_->gfxHighlighted[i].clear(); @@ -364,7 +483,7 @@ void FPGAViewWidget::onSelectedArchItem(std::vector<DecalXY> decals) { QMutexLocker locker(&rendererArgsLock_); rendererArgs_->selectedDecals = decals; - rendererArgs_->highlightedOrSelectedChanged = true; + rendererArgs_->changed = true; } pokeRenderer(); } @@ -374,33 +493,103 @@ void FPGAViewWidget::onHighlightGroupChanged(std::vector<DecalXY> decals, int gr { QMutexLocker locker(&rendererArgsLock_); rendererArgs_->highlightedDecals[group] = decals; - rendererArgs_->highlightedOrSelectedChanged = true; + rendererArgs_->changed = true; } pokeRenderer(); } void FPGAViewWidget::resizeGL(int width, int height) {} +boost::optional<FPGAViewWidget::PickedElement> FPGAViewWidget::pickElement(float worldx, float worldy) +{ + // Get elements from renderer whose BBs correspond to the pick. + std::vector<PickedElement> elems; + { + QMutexLocker locker(&rendererDataLock_); + if (rendererData_->qt == nullptr) { + return {}; + } + elems = rendererData_->qt->get(worldx, worldy); + } + + if (elems.size() == 0) { + return {}; + } + + // Calculate distances to all elements picked. + using ElemDist = std::pair<const PickedElement *, float>; + std::vector<ElemDist> distances; + std::transform(elems.begin(), elems.end(), std::back_inserter(distances), + [&](const PickedElement &e) -> ElemDist { + return std::make_pair(&e, e.distance(ctx_, worldx, worldy)); + }); + + // Find closest non -1 element. + auto closest = std::min_element(distances.begin(), distances.end(), [&](const ElemDist &a, const ElemDist &b){ + if (a.second == -1) return false; + if (b.second == -1) return true; + return a.second < b.second; + }); + + // All out of reach? + if (closest->second < 0) { + return {}; + } + + return *(closest->first); +} + void FPGAViewWidget::mousePressEvent(QMouseEvent *event) { if (event->buttons() & Qt::RightButton || event->buttons() & Qt::MidButton) { lastDragPos_ = event->pos(); } if (event->buttons() & Qt::LeftButton) { - int x = event->x(); - int y = event->y(); - auto world = mouseToWorldCoordinates(x, y); - rendererDataLock_.lock(); - //if (rendererData_->qtBels != nullptr) { - // auto elems = rendererData_->qtBels->get(world.x(), world.y()); - // if (elems.size() > 0) { - // clickedBel(elems[0]); - // } - //} - rendererDataLock_.unlock(); + auto world = mouseToWorldCoordinates(event->x(), event->y()); + auto closestOr = pickElement(world.x(), world.y()); + if (!closestOr) + return; + + auto closest = closestOr.value(); + if (closest.type == ElementType::BEL) { + clickedBel(closest.element.bel); + } else if (closest.type == ElementType::WIRE) { + clickedWire(closest.element.wire); + } + } +} + +void FPGAViewWidget::mouseMoveEvent(QMouseEvent *event) +{ + if (event->buttons() & Qt::RightButton || event->buttons() & Qt::MidButton) { + const int dx = event->x() - lastDragPos_.x(); + const int dy = event->y() - lastDragPos_.y(); + lastDragPos_ = event->pos(); + + auto world = mouseToWorldDimensions(dx, dy); + viewMove_.translate(world.x(), -world.y()); + + update(); + return; + } + + auto world = mouseToWorldCoordinates(event->x(), event->y()); + auto closestOr = pickElement(world.x(), world.y()); + if (!closestOr) + return; + + auto closest = closestOr.value(); + + { + QMutexLocker locked(&rendererArgsLock_); + rendererArgs_->hoveredDecal = closest.decal(ctx_); + rendererArgs_->changed = true; + pokeRenderer(); } + update(); } + // Invert the projection matrix to calculate screen/mouse to world/grid // coordinates. QVector4D FPGAViewWidget::mouseToWorldCoordinates(int x, int y) @@ -423,7 +612,7 @@ QVector4D FPGAViewWidget::mouseToWorldCoordinates(int x, int y) return world; } -QVector4D FPGAViewWidget::mouseToWorldDimensions(int x, int y) +QVector4D FPGAViewWidget::mouseToWorldDimensions(float x, float y) { QMatrix4x4 p = getProjection(); QVector2D unit = p.map(QVector4D(1, 1, 0, 1)).toVector2DAffine(); @@ -433,20 +622,6 @@ QVector4D FPGAViewWidget::mouseToWorldDimensions(int x, int y) return QVector4D(sx / unit.x(), sy / unit.y(), 0, 1); } -void FPGAViewWidget::mouseMoveEvent(QMouseEvent *event) -{ - if (event->buttons() & Qt::RightButton || event->buttons() & Qt::MidButton) { - const int dx = event->x() - lastDragPos_.x(); - const int dy = event->y() - lastDragPos_.y(); - lastDragPos_ = event->pos(); - - auto world = mouseToWorldDimensions(dx, dy); - viewMove_.translate(world.x(), -world.y()); - - update(); - } -} - void FPGAViewWidget::wheelEvent(QWheelEvent *event) { QPoint degree = event->angleDelta() / 8; diff --git a/gui/fpgaviewwidget.h b/gui/fpgaviewwidget.h index 3ab32dbf..b1eda33a 100644 --- a/gui/fpgaviewwidget.h +++ b/gui/fpgaviewwidget.h @@ -20,6 +20,7 @@ #ifndef MAPGLWIDGET_H #define MAPGLWIDGET_H +#include <boost/optional.hpp> #include <QMainWindow> #include <QMutex> #include <QOpenGLBuffer> @@ -120,6 +121,7 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions Q_SIGNALS: void clickedBel(BelId bel); + void clickedWire(WireId wire); private: const float zoomNear_ = 1.0f; // do not zoom closer than this @@ -127,7 +129,49 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions const float zoomLvl1_ = 100.0f; const float zoomLvl2_ = 50.0f; - using QuadTreeElements = QuadTree<float, std::pair<ElementType, IdString>>; + struct PickedElement { + ElementType type; + union Inner { + BelId bel; + WireId wire; + PipId pip; + GroupId group; + + Inner(BelId _bel) : bel(_bel) {} + Inner(WireId _wire) : wire(_wire) {} + Inner(PipId _pip) : pip(_pip) {} + Inner(GroupId _group) : group(_group) {} + } element; + float x, y; // Decal X and Y + PickedElement(BelId bel, float x, float y) : type(ElementType::BEL), element(bel), x(x), y(y) {} + PickedElement(WireId wire, float x, float y) : type(ElementType::WIRE), element(wire), x(x), y(y) {} + PickedElement(PipId pip, float x, float y) : type(ElementType::PIP), element(pip), x(x), y(y) {} + PickedElement(GroupId group, float x, float y) : type(ElementType::GROUP), element(group), x(x), y(y) {} + + DecalXY decal(Context *ctx) const + { + DecalXY decal; + switch (type) { + case ElementType::BEL: + decal = ctx->getBelDecal(element.bel); + break; + case ElementType::WIRE: + decal = ctx->getWireDecal(element.wire); + break; + case ElementType::PIP: + decal = ctx->getPipDecal(element.pip); + break; + case ElementType::GROUP: + decal = ctx->getGroupDecal(element.group); + break; + default: + NPNR_ASSERT_FALSE("Invalid ElementType"); + } + return decal; + } + float distance(Context *ctx, float wx, float wy) const; + }; + using PickQuadTree = QuadTree<float, PickedElement>; Context *ctx_; QTimer paintTimer_; @@ -147,6 +191,7 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions QColor inactive; QColor active; QColor selected; + QColor hovered; QColor highlight[8]; } colors_; @@ -154,11 +199,12 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions { LineShaderData gfxByStyle[GraphicElement::STYLE_MAX]; LineShaderData gfxSelected; + LineShaderData gfxHovered; LineShaderData gfxHighlighted[8]; // Global bounding box of data from Arch. float bbX0, bbY0, bbX1, bbY1; // Quadtree for picking objects. - std::unique_ptr<QuadTreeElements> qt; + std::unique_ptr<PickQuadTree> qt; }; std::unique_ptr<RendererData> rendererData_; QMutex rendererDataLock_; @@ -167,7 +213,8 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions { std::vector<DecalXY> selectedDecals; std::vector<DecalXY> highlightedDecals[8]; - bool highlightedOrSelectedChanged; + DecalXY hoveredDecal; + bool changed; }; std::unique_ptr<RendererArgs> rendererArgs_; QMutex rendererArgsLock_; @@ -177,9 +224,10 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions void renderGraphicElement(RendererData *data, LineShaderData &out, const GraphicElement &el, float x, float y); void renderDecal(RendererData *data, LineShaderData &out, const DecalXY &decal); void renderArchDecal(RendererData *data, const DecalXY &decal); - void populateQuadTree(RendererData *data, const DecalXY &decal, IdString id); + void populateQuadTree(RendererData *data, const DecalXY &decal, const PickedElement& element); + boost::optional<PickedElement> pickElement(float worldx, float worldy); QVector4D mouseToWorldCoordinates(int x, int y); - QVector4D mouseToWorldDimensions(int x, int y); + QVector4D mouseToWorldDimensions(float x, float y); QMatrix4x4 getProjection(void); }; |