From 155fef9f1655ad08360e9c6493a2f664d92ddf68 Mon Sep 17 00:00:00 2001 From: Sergiusz Bazanski Date: Wed, 25 Jul 2018 13:51:04 +0100 Subject: ice40: fix wire_num in tests --- tests/ice40/hx1k.cc | 2 +- tests/ice40/hx8k.cc | 2 +- tests/ice40/lp1k.cc | 2 +- tests/ice40/lp384.cc | 2 +- tests/ice40/lp8k.cc | 2 +- tests/ice40/up5k.cc | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/ice40/hx1k.cc b/tests/ice40/hx1k.cc index 6c3205f3..b7990a82 100644 --- a/tests/ice40/hx1k.cc +++ b/tests/ice40/hx1k.cc @@ -59,7 +59,7 @@ TEST_F(HX1KTest, wire_names) assert(wire == ctx->getWireByName(name)); wire_count++; } - ASSERT_EQ(wire_count, 27682); + ASSERT_EQ(wire_count, 27690); } TEST_F(HX1KTest, pip_names) diff --git a/tests/ice40/hx8k.cc b/tests/ice40/hx8k.cc index 485a2d31..d5b489eb 100644 --- a/tests/ice40/hx8k.cc +++ b/tests/ice40/hx8k.cc @@ -59,7 +59,7 @@ TEST_F(HX8KTest, wire_names) assert(wire == ctx->getWireByName(name)); wire_count++; } - ASSERT_EQ(wire_count, 135174); + ASSERT_EQ(wire_count, 135182); } TEST_F(HX8KTest, pip_names) diff --git a/tests/ice40/lp1k.cc b/tests/ice40/lp1k.cc index b1092700..b258d115 100644 --- a/tests/ice40/lp1k.cc +++ b/tests/ice40/lp1k.cc @@ -59,7 +59,7 @@ TEST_F(LP1KTest, wire_names) assert(wire == ctx->getWireByName(name)); wire_count++; } - ASSERT_EQ(wire_count, 27682); + ASSERT_EQ(wire_count, 27690); } TEST_F(LP1KTest, pip_names) diff --git a/tests/ice40/lp384.cc b/tests/ice40/lp384.cc index 287293d9..a2d91e7d 100644 --- a/tests/ice40/lp384.cc +++ b/tests/ice40/lp384.cc @@ -59,7 +59,7 @@ TEST_F(LP384Test, wire_names) assert(wire == ctx->getWireByName(name)); wire_count++; } - ASSERT_EQ(wire_count, 8294); + ASSERT_EQ(wire_count, 8302); } TEST_F(LP384Test, pip_names) diff --git a/tests/ice40/lp8k.cc b/tests/ice40/lp8k.cc index efe61b5b..a1c8c88c 100644 --- a/tests/ice40/lp8k.cc +++ b/tests/ice40/lp8k.cc @@ -59,7 +59,7 @@ TEST_F(LP8KTest, wire_names) assert(wire == ctx->getWireByName(name)); wire_count++; } - ASSERT_EQ(wire_count, 135174); + ASSERT_EQ(wire_count, 135182); } TEST_F(LP8KTest, pip_names) diff --git a/tests/ice40/up5k.cc b/tests/ice40/up5k.cc index 342a7c0a..6761db2e 100644 --- a/tests/ice40/up5k.cc +++ b/tests/ice40/up5k.cc @@ -59,7 +59,7 @@ TEST_F(UP5KTest, wire_names) assert(wire == ctx->getWireByName(name)); wire_count++; } - ASSERT_EQ(wire_count, 103383); + ASSERT_EQ(wire_count, 103391); } TEST_F(UP5KTest, pip_names) -- cgit v1.2.3 From 30d481e3215f33eff80e73c1ee0ed9cc3a8834f0 Mon Sep 17 00:00:00 2001 From: Sergiusz Bazanski Date: Wed, 25 Jul 2018 21:57:20 +0100 Subject: gui: Add QuadTree and tests --- CMakeLists.txt | 6 +- gui/quadtree.h | 387 ++++++++++++++++++++++++++++++++++++++++++++++++++ tests/gui/quadtree.cc | 127 +++++++++++++++++ 3 files changed, 519 insertions(+), 1 deletion(-) create mode 100644 gui/quadtree.h create mode 100644 tests/gui/quadtree.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index 55e57763..cc712e72 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -191,8 +191,12 @@ foreach (family ${ARCH}) # Add any new per-architecture targets here if (BUILD_TESTS) aux_source_directory(tests/${family}/ ${ufamily}_TEST_FILES) + if (BUILD_GUI) + aux_source_directory(tests/gui/ GUI_TEST_FILES) + endif() - add_executable(nextpnr-${family}-test ${${ufamily}_TEST_FILES} ${COMMON_FILES} ${${ufamily}_FILES}) + add_executable(nextpnr-${family}-test ${${ufamily}_TEST_FILES} + ${COMMON_FILES} ${${ufamily}_FILES} ${GUI_TEST_FILES}) target_link_libraries(nextpnr-${family}-test PRIVATE gtest_main) add_sanitizers(nextpnr-${family}-test) diff --git a/gui/quadtree.h b/gui/quadtree.h new file mode 100644 index 00000000..2b5a9df5 --- /dev/null +++ b/gui/quadtree.h @@ -0,0 +1,387 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Serge Bazanski + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef QUADTREE_H +#define QUADTREE_H + +// This file implements a quad tree used for comitting 2D axis aligned +// bounding boxes and then retrieving them by 2D point. + +NEXTPNR_NAMESPACE_BEGIN + +// A node of a QuadTree. Internal. +template +class QuadTreeNode +{ + public: + class BoundingBox { + friend class QuadTreeNode; + private: + CoordinateT x0_, x1_, y0_, y1_; + public: + // Standard constructor for a given (x0,y0), (x1,y1) bounding box + // + // @param x0 x coordinate of top-left corner of box + // @param y0 y coordinate of top-left corner of box + // @param x1 x coordinate of bottom-right corner of box + // @param y1 y coordinate of bottom-right corner of box + BoundingBox(CoordinateT x0, CoordinateT y0, CoordinateT x1, CoordinateT y1) : + x0_(x0), x1_(x1), y0_(y0), y1_(y1) {} + + BoundingBox(const BoundingBox &other) : + x0_(other.x0_), x1_(other.x1_), y0_(other.y0_), y1_(other.y1_) {} + + // Whether a bounding box contains a given points. + // A point is defined to be in a bounding box when it's not lesser than + // the lower coordinate or greater than the higher coordinate, eg: + // A BoundingBox of x0: 20, y0: 30, x1: 100, y1: 130 fits the following + // points: + // [ (50, 50), (20, 50), (20, 30), (100, 130) ] + inline bool contains(CoordinateT x, CoordinateT y) const + { + if (x < x0_ || x > x1_) + return false; + if (y < y0_ || y > y1_) + return false; + return true; + } + }; + + private: + // A pair of Element and BoundingBox that contains it. + class BoundElement { + friend class QuadTreeNode; + private: + BoundingBox bb_; + ElementT elem_; + public: + BoundElement(BoundingBox bb, ElementT elem) : + bb_(bb), elem_(elem) {} + }; + + // The bounding box that this node describes. + BoundingBox bound_; + // How many elements should be contained in this node until it splits into + // sub-nodes. + const size_t max_elems_; + // Four sub-nodes or nullptr if it hasn't split yet. + std::unique_ptr[]> children_ = nullptr; + // Coordinates of the split. + // Anything < split_x is west. + CoordinateT splitx_; + // Anything < split_y is north. + CoordinateT splity_; + + // Elements contained directly within this node and not part of children + // nodes. + std::vector elems_; + // Depth at which this node is - root is at 0, first level at 1, etc. + int depth_; + + // Checks whether a given bounding box fits within this node - used for + // sanity checking on insertion. + // @param b bounding box to check + // @returns whether b fits in this node entirely + bool fits(const BoundingBox &b) const + { + if (b.x0_ < bound_.x0_ || b.x0_ > bound_.x1_) { + return false; + } else if (b.x1_ < bound_.x0_ || b.x1_ > bound_.x1_) { + return false; + } else if (b.y0_ < bound_.y0_ || b.y0_ > bound_.y1_) { + return false; + } else if (b.y1_ < bound_.y0_ || b.y1_ > bound_.y1_) { + return false; + } + return true; + } + + // Used to describe one of 5 possible places an element can exist: + // - the node itself (THIS) + // - any of the 4 children nodes. + enum Quadrant { + THIS = -1, + NW = 0, + NE = 1, + SW = 2, + SE = 3 + }; + + // Finds the quadrant to which a bounding box should go (if the node + // is / were to be split). + // @param b bounding box to check + // @returns quadrant in which b belongs to if the node is were to be split + Quadrant quadrant(const BoundingBox &b) const + { + if (children_ == nullptr) { + return THIS; + } + + bool west0 = b.x0_ < splitx_; + bool west1 = b.x1_ < splitx_; + bool north0 = b.y0_ < splity_; + bool north1 = b.y1_ < splity_; + + if (west0 && west1 && north0 && north1) + return NW; + if (!west0 && !west1 && north0 && north1) + return NE; + if (west0 && west1 && !north0 && !north1) + return SW; + if (!west0 && !west1 && !north0 && !north1) + return SE; + return THIS; + } + + // Checks whether this node should split. + bool should_split() const + { + // The node shouldn't split if it's not large enough to merit it. + if (elems_.size() < max_elems_) + return false; + + // The node shouldn't split if its' level is too deep (this is true for + // 100k+ entries, where the amount of splits causes us to lose + // significant CPU time on traversing the tree, or worse yet causes a + // stack overflow). + if (depth_ > 5) + return false; + + return true; + } + + + public: + // Standard constructor for node. + // @param b BoundingBox this node covers. + // @param depth depth at which this node is in the tree + // @max_elems how many elements should this node contain before it splits + QuadTreeNode(BoundingBox b, int depth, size_t max_elems = 4) : + bound_(b), max_elems_(max_elems), depth_(depth) + { + } + // Disallow copies. + QuadTreeNode(const QuadTreeNode &other) = delete; + QuadTreeNode &operator=(const QuadTreeNode &other) = delete; + // Allow moves. + QuadTreeNode(QuadTreeNode &&other) : + bound_(other.bound_), max_elems_(other.max_elems_), children_(std::move(other.children_)), + splitx_(other.splitx_), splity_(other.splity_), elems_(std::move(other.elems_)), depth_(other.depth_) + { + other.children_ = nullptr; + } + QuadTreeNode &operator=(QuadTreeNode &&other) + { + if (this == &other) + return *this; + bound_ = other.bound_; + max_elems_ = other.max_elems_; + children_ = other.max_children_; + children_ = other.children_; + splitx_ = other.splitx_; + splity_ = other.splity_; + elems_ = std::move(other.elems_); + depth_ = other.depth_; + other.children_ = nullptr; + return *this; + } + + // Insert an element at a given bounding box. + bool insert(const BoundingBox &k, ElementT v) + { + // Fail early if this BB doesn't fit us at all. + if (!fits(k)) { + return false; + } + + // Do we have children? + if (children_ != nullptr) { + // Put the element either recursively into a child if it fits + // entirely or keep it for ourselves if not. + auto quad = quadrant(k); + if (quad == THIS) { + elems_.push_back(BoundElement(k, std::move(v))); + } else { + return children_[quad].insert(k, std::move(v)); + } + } else { + // No children and not about to have any. + if (!should_split()) { + elems_.push_back(BoundElement(k, std::move(v))); + return true; + } + // Splitting. Calculate the split point. + splitx_ = (bound_.x1_ - bound_.x0_) / 2 + bound_.x0_; + splity_ = (bound_.y1_ - bound_.y0_) / 2 + bound_.y0_; + // Create the new children. + children_ = decltype(children_)(new QuadTreeNode[4] { + // Note: not using [NW] = QuadTreeNode because that seems to + // crash g++ 7.3.0. + /* NW */ QuadTreeNode(BoundingBox(bound_.x0_, bound_.y0_, splitx_, splity_), depth_+1, max_elems_), + /* NE */ QuadTreeNode(BoundingBox(splitx_, bound_.y0_, bound_.x1_, splity_), depth_+1, max_elems_), + /* SW */ QuadTreeNode(BoundingBox(bound_.x0_, splity_, splitx_, bound_.y1_), depth_+1, max_elems_), + /* SE */ QuadTreeNode(BoundingBox(splitx_, splity_, bound_.x1_, bound_.y1_), depth_+1, max_elems_), + }); + // Move all elements to where they belong. + auto it = elems_.begin(); + while (it != elems_.end()) { + auto quad = quadrant(it->bb_); + if (quad != THIS) { + // Move to one of the children. + if (!children_[quad].insert(it->bb_, std::move(it->elem_))) + return false; + // Delete from ourselves. + it = elems_.erase(it); + } else { + // Keep for ourselves. + it++; + } + } + // Insert the actual element now that we've split. + return insert(k, std::move(v)); + } + return true; + } + + // Dump a human-readable representation of the tree to stdout. + void dump(int level) const + { + for (int i = 0; i < level; i++) printf(" "); + printf("loc: % 3d % 3d % 3d % 3d\n", bound_.x0_, bound_.y0_, bound_.x1_, bound_.y1_); + if (elems_.size() != 0) { + for (int i = 0; i < level; i++) printf(" "); + printf("elems: %zu\n", elems_.size()); + } + + if (children_ != nullptr) { + for (int i = 0; i < level; i++) printf(" "); + printf("children:\n"); + children_[NW].dump(level+1); + children_[NE].dump(level+1); + children_[SW].dump(level+1); + children_[SE].dump(level+1); + } + } + + // Return count of BoundingBoxes/Elements contained. + // @returns count of elements contained. + size_t size(void) const + { + size_t res = elems_.size(); + if (children_ != nullptr) { + res += children_[NW].size(); + res += children_[NE].size(); + res += children_[SW].size(); + res += children_[SE].size(); + } + return res; + } + + // Retrieve elements whose bounding boxes cover the given coordinates. + // + // @param x X coordinate of points to query. + // @param y Y coordinate of points to query. + // @returns vector of found bounding boxes + void get(CoordinateT x, CoordinateT y, std::vector &res) const + { + if (!bound_.contains(x, y)) + return; + + for (const auto &elem : elems_) { + const auto &bb = elem.bb_; + if (bb.contains(x, y)) { + res.push_back(elem.elem_); + } + } + if (children_ != nullptr) { + children_[NW].get(x, y, res); + children_[NE].get(x, y, res); + children_[SW].get(x, y, res); + children_[SE].get(x, y, res); + } + } +}; + +// User facing method to manage a quad tree. +// +// @param CoodinateT scalar type of the coordinate system - int, float, ... +// @param ElementT type of the contained element. Must be movable or copiable. +template +class QuadTree +{ + private: + // Root of the tree. + QuadTreeNode root_; + + public: + // To let user create bounding boxes of the correct type. + // Bounding boxes are composed of two 2D points, which designate their + // top-left and bottom-right corners. All its' edges are axis aligned. + using BoundingBox = typename QuadTreeNode::BoundingBox; + + // Standard constructor. + // + // @param b Bounding box of the entire tree - all comitted elements must + // fit within in. + QuadTree(BoundingBox b) : + root_(b, 0) + { + } + + // Inserts a new value at a given bounding box.e + // BoundingBoxes are not deduplicated - if two are pushed with the same + // coordinates, the first one will take precendence. + // + // @param k Bounding box at which to store value. + // @param v Value at a given bounding box. + // @returns Whether the insert was succesful. + bool insert(const BoundingBox &k, ElementT v) + { + return root_.insert(k, v); + } + + // Dump a human-readable representation of the tree to stdout. + void dump(void) const + { + root_.dump(0); + } + + // Return count of BoundingBoxes/Elements contained. + // @returns count of elements contained. + size_t size(void) const + { + return root_.size(); + } + + // Retrieve elements whose bounding boxes cover the given coordinates. + // + // @param x X coordinate of points to query. + // @param y Y coordinate of points to query. + // @returns vector of found bounding boxes + std::vector get(CoordinateT x, CoordinateT y) const + { + std::vector res; + root_.get(x, y, res); + return std::move(res); + } +}; + +NEXTPNR_NAMESPACE_END + +#endif diff --git a/tests/gui/quadtree.cc b/tests/gui/quadtree.cc new file mode 100644 index 00000000..ca90a426 --- /dev/null +++ b/tests/gui/quadtree.cc @@ -0,0 +1,127 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Serge Bazanski + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "gtest/gtest.h" +#include "nextpnr.h" +#include "quadtree.h" + +USING_NEXTPNR_NAMESPACE + +using QT = QuadTree; + +class QuadTreeTest : public ::testing::Test +{ + protected: + virtual void SetUp() + { + qt_ = new QT(QT::BoundingBox(0, 0, width_, height_)); + } + virtual void TearDown() + { + delete qt_; + } + + int width_ = 100; + int height_ = 100; + QT *qt_; +}; + +// Test that we're doing bound checking correctly. +TEST_F(QuadTreeTest, insert_bound_checking) +{ + ASSERT_TRUE(qt_->insert(QT::BoundingBox(10, 10, 20, 20), 10)); + ASSERT_TRUE(qt_->insert(QT::BoundingBox(0, 0, 100, 100), 10)); + ASSERT_FALSE(qt_->insert(QT::BoundingBox(10, 10, 101, 20), 10)); + ASSERT_FALSE(qt_->insert(QT::BoundingBox(-1, 10, 101, 20), 10)); + ASSERT_FALSE(qt_->insert(QT::BoundingBox(-1, -1, 20, 20), 10)); +} + +// Test whether we are not losing any elements. +TEST_F(QuadTreeTest, insert_count) +{ + auto rng = NEXTPNR_NAMESPACE::DeterministicRNG(); + + // Add 10000 random rectangles. + for (int i = 0; i < 10000; i++) { + int x0 = rng.rng(width_); + int y0 = rng.rng(height_); + int w = rng.rng(width_ - x0); + int h = rng.rng(width_ - y0); + int x1 = x0 + w; + int y1 = y0 + h; + ASSERT_TRUE(qt_->insert(QT::BoundingBox(x0, y0, x1, y1), i)); + ASSERT_EQ(qt_->size(), i+1); + } + // Add 100000 random points. + for (int i = 0; i < 100000; i++) { + int x0 = rng.rng(width_); + int y0 = rng.rng(height_); + int x1 = x0; + int y1 = y0; + ASSERT_TRUE(qt_->insert(QT::BoundingBox(x0, y0, x1, y1), i)); + ASSERT_EQ(qt_->size(), i+10001); + } +} + +// Test that we can insert and retrieve the same element. +TEST_F(QuadTreeTest, insert_retrieve_same) +{ + auto rng = NEXTPNR_NAMESPACE::DeterministicRNG(); + + // Add 10000 small random rectangles. + rng.rngseed(0); + for (int i = 0; i < 10000; i++) { + int x0 = rng.rng(width_); + int y0 = rng.rng(height_); + int w = rng.rng(width_ - x0); + int h = rng.rng(width_ - y0); + int x1 = x0 + w/4; + int y1 = y0 + h/4; + ASSERT_TRUE(qt_->insert(QT::BoundingBox(x0, y0, x1, y1), i)); + } + + // Restart RNG, make sure we get the same rectangles back. + rng.rngseed(0); + for (int i = 0; i < 10000; i++) { + int x0 = rng.rng(width_); + int y0 = rng.rng(height_); + int w = rng.rng(width_ - x0); + int h = rng.rng(width_ - y0); + int x1 = x0 + w/4; + int y1 = y0 + h/4; + + // try to find something in the middle of the square + int x = (x1-x0)/2+x0; + int y = (y1-y0)/2+y0; + + auto res = qt_->get(x, y); + // Somewhat arbirary test to make sure we don't return obscene + // amounts of data. + ASSERT_LT(res.size(), 200); + bool found = false; + for (auto elem : res) { + // Is this what we're looking for? + if (elem == i) { + found = true; + break; + } + } + ASSERT_TRUE(found); + } +} -- cgit v1.2.3 From c897c0ca9afab1d758f5c1b77312e77057a4c814 Mon Sep 17 00:00:00 2001 From: Sergiusz Bazanski Date: Thu, 26 Jul 2018 16:20:58 +0100 Subject: gui: clickable bels, WIP --- gui/basewindow.cc | 1 + gui/designwidget.cc | 7 +++++ gui/designwidget.h | 1 + gui/fpgaviewwidget.cc | 74 ++++++++++++++++++++++++++++++++++++++++----------- gui/fpgaviewwidget.h | 20 ++++++++++++++ 5 files changed, 88 insertions(+), 15 deletions(-) diff --git a/gui/basewindow.cc b/gui/basewindow.cc index 78c2fe3a..3cb2cc96 100644 --- a/gui/basewindow.cc +++ b/gui/basewindow.cc @@ -83,6 +83,7 @@ BaseMainWindow::BaseMainWindow(std::unique_ptr context, QWidget *parent connect(this, SIGNAL(contextChanged(Context *)), fpgaView, SLOT(newContext(Context *))); connect(designview, SIGNAL(selected(std::vector)), fpgaView, SLOT(onSelectedArchItem(std::vector))); + connect(fpgaView, SIGNAL(clickedBel(BelId)), designview, SLOT(onClickedBel(BelId))); connect(designview, SIGNAL(highlight(std::vector, int)), fpgaView, SLOT(onHighlightGroupChanged(std::vector, int))); diff --git a/gui/designwidget.cc b/gui/designwidget.cc index 7e8e2840..6d2a6d3d 100644 --- a/gui/designwidget.cc +++ b/gui/designwidget.cc @@ -495,6 +495,13 @@ QtProperty *DesignWidget::addSubGroup(QtProperty *topItem, const QString &name) return item; } +void DesignWidget::onClickedBel(BelId bel) +{ + QTreeWidgetItem *item = nameToItem[getElementIndex(ElementType::BEL)].value(ctx->getBelName(bel).c_str(ctx)); + treeWidget->setCurrentItem(item); + Q_EMIT selected(getDecals(ElementType::BEL, ctx->getBelName(bel))); +} + void DesignWidget::onItemSelectionChanged() { if (treeWidget->selectedItems().size() == 0) diff --git a/gui/designwidget.h b/gui/designwidget.h index b5877f60..61681541 100644 --- a/gui/designwidget.h +++ b/gui/designwidget.h @@ -74,6 +74,7 @@ class DesignWidget : public QWidget public Q_SLOTS: void newContext(Context *ctx); void updateTree(); + void onClickedBel(BelId bel); private: Context *ctx; diff --git a/gui/fpgaviewwidget.cc b/gui/fpgaviewwidget.cc index 15f37ce0..b9e05a04 100644 --- a/gui/fpgaviewwidget.cc +++ b/gui/fpgaviewwidget.cc @@ -314,11 +314,10 @@ void FPGAViewWidget::initializeGL() void FPGAViewWidget::drawDecal(LineShaderData &out, const DecalXY &decal) { const float scale = 1.0; - float offsetX = 0.0, offsetY = 0.0; + float offsetX = decal.x; + float offsetY = decal.y; for (auto &el : ctx_->getDecalGraphics(decal.decal)) { - offsetX = decal.x; - offsetY = decal.y; if (el.type == GraphicElement::G_BOX) { auto line = PolyLine(true); @@ -400,8 +399,8 @@ void FPGAViewWidget::paintGL() matrix *= viewMove_; // Calculate world thickness to achieve a screen 1px/1.1px line. - float thick1Px = mouseToWorldCoordinates(1, 0).x(); - float thick11Px = mouseToWorldCoordinates(1.1, 0).x(); + float thick1Px = mouseToWorldDimensions(1, 0).x(); + float thick11Px = mouseToWorldDimensions(1.1, 0).x(); // Draw grid. auto grid = LineShaderData(); @@ -462,13 +461,14 @@ void FPGAViewWidget::renderLines(void) } // Local copy of decals, taken as fast as possible to not block the P&R. - std::vector belDecals; + std::vector> belDecals; std::vector wireDecals; std::vector pipDecals; std::vector groupDecals; + if (decalsChanged) { for (auto bel : ctx_->getBels()) { - belDecals.push_back(ctx_->getBelDecal(bel)); + belDecals.push_back(std::pair(bel, ctx_->getBelDecal(bel))); } for (auto wire : ctx_->getWires()) { wireDecals.push_back(ctx_->getWireDecal(wire)); @@ -489,11 +489,15 @@ void FPGAViewWidget::renderLines(void) rendererArgs_->highlightedOrSelectedChanged = false; rendererArgsLock_.unlock(); + QuadTreeBels::BoundingBox globalBB = QuadTreeBels::BoundingBox(-1000, -1000, 1000, 1000); + if (decalsChanged) { auto data = std::unique_ptr(new FPGAViewWidget::RendererData); // Draw Bels. + data->qtBels = std::unique_ptr(new QuadTreeBels(globalBB)); for (auto const &decal : belDecals) { - drawDecal(data->decals, decal); + drawDecal(data->decals, decal.second); + commitToQuadtree(data->qtBels.get(), decal.second, decal.first); } // Draw Wires. for (auto const &decal : wireDecals) { @@ -550,11 +554,49 @@ void FPGAViewWidget::onHighlightGroupChanged(std::vector decals, int gr void FPGAViewWidget::resizeGL(int width, int height) {} -void FPGAViewWidget::mousePressEvent(QMouseEvent *event) { lastPos_ = event->pos(); } +void FPGAViewWidget::mousePressEvent(QMouseEvent *event) +{ + if (event->buttons() & Qt::RightButton || event->buttons() & Qt::MidButton) { + lastPos_ = 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(); + } +} // Invert the projection matrix to calculate screen/mouse to world/grid // coordinates. QVector4D FPGAViewWidget::mouseToWorldCoordinates(int x, int y) +{ + const qreal retinaScale = devicePixelRatio(); + + auto projection = getProjection(); + + QMatrix4x4 vp; + vp.viewport(0, 0, width() * retinaScale, height() * retinaScale); + + QVector4D vec(x, y, 0, 1); + vec = vp.inverted() * vec; + vec = projection.inverted() * vec; + + auto ray = vec.toVector3DAffine(); + auto world = QVector4D(ray.x()*ray.z(), -ray.y()*ray.z(), 0, 1); + world = viewMove_.inverted() * world; + + return world; +} + +QVector4D FPGAViewWidget::mouseToWorldDimensions(int x, int y) { QMatrix4x4 p = getProjection(); QVector2D unit = p.map(QVector4D(1, 1, 0, 1)).toVector2DAffine(); @@ -566,14 +608,16 @@ QVector4D FPGAViewWidget::mouseToWorldCoordinates(int x, int y) void FPGAViewWidget::mouseMoveEvent(QMouseEvent *event) { - const int dx = event->x() - lastPos_.x(); - const int dy = event->y() - lastPos_.y(); - lastPos_ = event->pos(); + if (event->buttons() & Qt::RightButton || event->buttons() & Qt::MidButton) { + const int dx = event->x() - lastPos_.x(); + const int dy = event->y() - lastPos_.y(); + lastPos_ = event->pos(); - auto world = mouseToWorldCoordinates(dx, dy); - viewMove_.translate(world.x(), -world.y()); + auto world = mouseToWorldDimensions(dx, dy); + viewMove_.translate(world.x(), -world.y()); - update(); + update(); + } } void FPGAViewWidget::wheelEvent(QWheelEvent *event) diff --git a/gui/fpgaviewwidget.h b/gui/fpgaviewwidget.h index b87c5d0a..f69fa786 100644 --- a/gui/fpgaviewwidget.h +++ b/gui/fpgaviewwidget.h @@ -33,6 +33,7 @@ #include #include "nextpnr.h" +#include "quadtree.h" NEXTPNR_NAMESPACE_BEGIN @@ -292,6 +293,8 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions void onSelectedArchItem(std::vector decals); void onHighlightGroupChanged(std::vector decals, int group); void pokeRenderer(void); + Q_SIGNALS: + void clickedBel(BelId bel); private: void renderLines(void); @@ -302,6 +305,7 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions float zoom_; QMatrix4x4 getProjection(void); QVector4D mouseToWorldCoordinates(int x, int y); + QVector4D mouseToWorldDimensions(int x, int y); const float zoomNear_ = 1.0f; // do not zoom closer than this const float zoomFar_ = 10000.0f; // do not zoom further than this @@ -314,6 +318,21 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions std::unique_ptr renderRunner_; + using QuadTreeBels = QuadTree; + + template + void commitToQuadtree(T *tree, const DecalXY &decal, BelId bel) + { + float offsetX = decal.x; + float offsetY = decal.y; + + for (auto &el : ctx_->getDecalGraphics(decal.decal)) { + if (el.type == GraphicElement::G_BOX) { + tree->insert(typename T::BoundingBox(offsetX + el.x1, offsetY + el.y1, offsetX + el.x2, offsetY + el.y2), bel); + } + } + } + struct { QColor background; @@ -331,6 +350,7 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions LineShaderData decals[4]; LineShaderData selected; LineShaderData highlighted[8]; + std::unique_ptr qtBels; }; struct RendererArgs -- cgit v1.2.3 From 567566585ce9c4e15bd72f914084a92f7a62d553 Mon Sep 17 00:00:00 2001 From: Sergiusz Bazanski Date: Thu, 26 Jul 2018 18:43:00 +0100 Subject: wip --- gui/fpgaviewwidget.cc | 117 ++++++++++++++++++++++++++++++-------------------- gui/fpgaviewwidget.h | 30 +++++-------- 2 files changed, 82 insertions(+), 65 deletions(-) diff --git a/gui/fpgaviewwidget.cc b/gui/fpgaviewwidget.cc index ed3a0bce..fef6c328 100644 --- a/gui/fpgaviewwidget.cc +++ b/gui/fpgaviewwidget.cc @@ -102,36 +102,40 @@ void FPGAViewWidget::initializeGL() 0.0); } -void FPGAViewWidget::drawGraphicElement(LineShaderData &out, const GraphicElement &el, float x, float y) +void FPGAViewWidget::renderGraphicElement(RendererData *data, LineShaderData &out, const GraphicElement &el, float x, float y) { - const float scale = 1.0; - if (el.type == GraphicElement::TYPE_BOX) { auto line = PolyLine(true); - line.point(x + scale * el.x1, y + scale * el.y1); - line.point(x + scale * el.x2, y + scale * el.y1); - line.point(x + scale * el.x2, y + scale * el.y2); - line.point(x + scale * el.x1, y + scale * el.y2); + line.point(x + el.x1, y + el.y1); + line.point(x + el.x2, y + el.y1); + line.point(x + el.x2, y + el.y2); + line.point(x + el.x1, y + el.y2); line.build(out); + + data->bbX0 = std::min(data->bbX0, x + el.x1); + data->bbY0 = std::min(data->bbY0, x + el.y1); + data->bbX1 = std::max(data->bbX1, x + el.x2); + data->bbY1 = std::max(data->bbY1, x + el.y2); + return; } if (el.type == GraphicElement::TYPE_LINE || el.type == GraphicElement::TYPE_ARROW) { - PolyLine(x + scale * el.x1, y + scale * el.y1, x + scale * el.x2, y + scale * el.y2) - .build(out); + PolyLine(x + el.x1, y + el.y1, x + el.x2, y + el.y2).build(out); + return; } } -void FPGAViewWidget::drawDecal(LineShaderData &out, const DecalXY &decal) +void FPGAViewWidget::renderDecal(RendererData *data, LineShaderData &out, const DecalXY &decal) { float offsetX = decal.x; float offsetY = decal.y; for (auto &el : ctx_->getDecalGraphics(decal.decal)) { - drawGraphicElement(out, el, offsetX, offsetY); + renderGraphicElement(data, out, el, offsetX, offsetY); } } -void FPGAViewWidget::drawArchDecal(LineShaderData out[GraphicElement::STYLE_MAX], const DecalXY &decal) +void FPGAViewWidget::renderArchDecal(RendererData *data, const DecalXY &decal) { float offsetX = decal.x; float offsetY = decal.y; @@ -141,7 +145,7 @@ void FPGAViewWidget::drawArchDecal(LineShaderData out[GraphicElement::STYLE_MAX] case GraphicElement::STYLE_FRAME: case GraphicElement::STYLE_INACTIVE: case GraphicElement::STYLE_ACTIVE: - drawGraphicElement(out[el.style], el, offsetX, offsetY); + renderGraphicElement(data, data->gfxByStyle[el.style], el, offsetX, offsetY); break; default: break; @@ -149,6 +153,10 @@ void FPGAViewWidget::drawArchDecal(LineShaderData out[GraphicElement::STYLE_MAX] } } +void FPGAViewWidget::populateQuadTree(RendererData *data, const DecalXY &decal, IdString id) +{ +} + QMatrix4x4 FPGAViewWidget::getProjection(void) { QMatrix4x4 matrix; @@ -183,20 +191,21 @@ void FPGAViewWidget::paintGL() // Draw grid. lineShader_.draw(grid, colors_.grid, thick1Px, matrix); - rendererDataLock_.lock(); + { + QMutexLocker locker(&rendererDataLock_); - // Render Arch graphics. - lineShader_.draw(rendererData_->gfxByStyle[GraphicElement::STYLE_FRAME], colors_.frame, thick11Px, matrix); - lineShader_.draw(rendererData_->gfxByStyle[GraphicElement::STYLE_HIDDEN], colors_.hidden, thick11Px, matrix); - lineShader_.draw(rendererData_->gfxByStyle[GraphicElement::STYLE_INACTIVE], colors_.inactive, thick11Px, matrix); - lineShader_.draw(rendererData_->gfxByStyle[GraphicElement::STYLE_ACTIVE], colors_.active, thick11Px, matrix); + // Render Arch graphics. + lineShader_.draw(rendererData_->gfxByStyle[GraphicElement::STYLE_FRAME], colors_.frame, thick11Px, matrix); + lineShader_.draw(rendererData_->gfxByStyle[GraphicElement::STYLE_HIDDEN], colors_.hidden, thick11Px, matrix); + lineShader_.draw(rendererData_->gfxByStyle[GraphicElement::STYLE_INACTIVE], colors_.inactive, thick11Px, matrix); + lineShader_.draw(rendererData_->gfxByStyle[GraphicElement::STYLE_ACTIVE], colors_.active, thick11Px, matrix); - // Draw highlighted items. - for (int i = 0; i < 8; i++) - lineShader_.draw(rendererData_->gfxHighlighted[i], colors_.highlight[i], thick11Px, matrix); + // Draw highlighted items. + for (int i = 0; i < 8; i++) + lineShader_.draw(rendererData_->gfxHighlighted[i], colors_.highlight[i], thick11Px, matrix); - lineShader_.draw(rendererData_->gfxSelected, colors_.selected, thick11Px, matrix); - rendererDataLock_.unlock(); + lineShader_.draw(rendererData_->gfxSelected, colors_.selected, thick11Px, matrix); + } } void FPGAViewWidget::pokeRenderer(void) { renderRunner_->poke(); } @@ -207,10 +216,10 @@ void FPGAViewWidget::renderLines(void) return; // Data from Context needed to render all decals. - std::vector belDecals; - std::vector wireDecals; - std::vector pipDecals; - std::vector groupDecals; + std::vector> belDecals; + std::vector> wireDecals; + std::vector> pipDecals; + std::vector> groupDecals; bool decalsChanged = false; { // Take the UI/Normal mutex on the Context, copy over all we need as @@ -248,16 +257,16 @@ 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)); + belDecals.push_back({ctx_->getBelDecal(bel), ctx_->getBelName(bel)}); } for (auto wire : ctx_->getWires()) { - wireDecals.push_back(ctx_->getWireDecal(wire)); + wireDecals.push_back({ctx_->getWireDecal(wire), ctx_->getWireName(wire)}); } for (auto pip : ctx_->getPips()) { - pipDecals.push_back(ctx_->getPipDecal(pip)); + pipDecals.push_back({ctx_->getPipDecal(pip), ctx_->getPipName(pip)}); } for (auto group : ctx_->getGroups()) { - groupDecals.push_back(ctx_->getGroupDecal(group)); + groupDecals.push_back({ctx_->getGroupDecal(group), ctx_->getGroupName(group)}); } } } @@ -276,29 +285,45 @@ void FPGAViewWidget::renderLines(void) rendererArgs_->highlightedOrSelectedChanged = false; } - QuadTreeBels::BoundingBox globalBB = QuadTreeBels::BoundingBox(-1000, -1000, 1000, 1000); // Render decals if necessary. if (decalsChanged) { auto data = std::unique_ptr(new FPGAViewWidget::RendererData); + // Reset bounding box. + data->bbX0 = 0; + data->bbY0 = 0; + data->bbX1 = 0; + data->bbY1 = 0; + // Draw Bels. - data->qtBels = std::unique_ptr(new QuadTreeBels(globalBB)); for (auto const &decal : belDecals) { - drawArchDecal(data->gfxByStyle, decal); + renderArchDecal(data.get(), decal.first); } // Draw Wires. for (auto const &decal : wireDecals) { - drawArchDecal(data->gfxByStyle, decal); + renderArchDecal(data.get(), decal.first); } // Draw Pips. for (auto const &decal : pipDecals) { - drawArchDecal(data->gfxByStyle, decal); + renderArchDecal(data.get(), decal.first); } // Draw Groups. for (auto const &decal : groupDecals) { - drawArchDecal(data->gfxByStyle, decal); + renderArchDecal(data.get(), decal.first); } + // 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); + + // Populate picking quadtree. + //data->qt = std::unique_ptr(new QuadTreeElements(bb)); + + //for (auto const &decal : belDecals) { + // populateQuadTree(data.get(), decal.first, decal.second); + //} + // Swap over. { QMutexLocker lock(&rendererDataLock_); @@ -321,14 +346,14 @@ void FPGAViewWidget::renderLines(void) // Render selected. rendererData_->gfxSelected.clear(); for (auto &decal : selectedDecals) { - drawDecal(rendererData_->gfxSelected, decal); + renderDecal(rendererData_.get(), rendererData_->gfxSelected, decal); } // Render highlighted. for (int i = 0; i < 8; i++) { rendererData_->gfxHighlighted[i].clear(); for (auto &decal : highlightedDecals[i]) { - drawDecal(rendererData_->gfxHighlighted[i], decal); + renderDecal(rendererData_.get(), rendererData_->gfxHighlighted[i], decal); } } } @@ -366,12 +391,12 @@ void FPGAViewWidget::mousePressEvent(QMouseEvent *event) 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]); - } - } + //if (rendererData_->qtBels != nullptr) { + // auto elems = rendererData_->qtBels->get(world.x(), world.y()); + // if (elems.size() > 0) { + // clickedBel(elems[0]); + // } + //} rendererDataLock_.unlock(); } } diff --git a/gui/fpgaviewwidget.h b/gui/fpgaviewwidget.h index a360eea7..3ab32dbf 100644 --- a/gui/fpgaviewwidget.h +++ b/gui/fpgaviewwidget.h @@ -35,6 +35,7 @@ #include "nextpnr.h" #include "quadtree.h" #include "lineshader.h" +#include "designwidget.h" NEXTPNR_NAMESPACE_BEGIN @@ -126,25 +127,12 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions const float zoomLvl1_ = 100.0f; const float zoomLvl2_ = 50.0f; + using QuadTreeElements = QuadTree>; + Context *ctx_; QTimer paintTimer_; std::unique_ptr renderRunner_; - using QuadTreeBels = QuadTree; - - template - void commitToQuadtree(T *tree, const DecalXY &decal, BelId bel) - { - float offsetX = decal.x; - float offsetY = decal.y; - - for (auto &el : ctx_->getDecalGraphics(decal.decal)) { - if (el.type == GraphicElement::TYPE_BOX) { - tree->insert(typename T::BoundingBox(offsetX + el.x1, offsetY + el.y1, offsetX + el.x2, offsetY + el.y2), bel); - } - } - } - QPoint lastDragPos_; LineShader lineShader_; QMatrix4x4 viewMove_; @@ -167,7 +155,10 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions LineShaderData gfxByStyle[GraphicElement::STYLE_MAX]; LineShaderData gfxSelected; LineShaderData gfxHighlighted[8]; - std::unique_ptr qtBels; + // Global bounding box of data from Arch. + float bbX0, bbY0, bbX1, bbY1; + // Quadtree for picking objects. + std::unique_ptr qt; }; std::unique_ptr rendererData_; QMutex rendererDataLock_; @@ -183,9 +174,10 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions void zoom(int level); void renderLines(void); - void drawGraphicElement(LineShaderData &out, const GraphicElement &el, float x, float y); - void drawDecal(LineShaderData &out, const DecalXY &decal); - void drawArchDecal(LineShaderData out[GraphicElement::STYLE_MAX], const DecalXY &decal); + 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); QVector4D mouseToWorldCoordinates(int x, int y); QVector4D mouseToWorldDimensions(int x, int y); QMatrix4x4 getProjection(void); -- cgit v1.2.3 From df908374dc233c23aef0790cbce65aa0a58c81ec Mon Sep 17 00:00:00 2001 From: Sergiusz Bazanski Date: Thu, 26 Jul 2018 22:40:45 +0100 Subject: gui: implement basic cursor picking --- common/nextpnr.h | 5 + gui/basewindow.cc | 1 + gui/designwidget.cc | 7 ++ gui/designwidget.h | 4 +- gui/fpgaviewwidget.cc | 267 +++++++++++++++++++++++++++++++++++++++++--------- gui/fpgaviewwidget.h | 58 ++++++++++- 6 files changed, 290 insertions(+), 52 deletions(-) diff --git a/common/nextpnr.h b/common/nextpnr.h index 5e32d5b6..908b8266 100644 --- a/common/nextpnr.h +++ b/common/nextpnr.h @@ -204,6 +204,11 @@ struct DecalXY { DecalId decal; float x = 0, y = 0; + + bool operator==(const DecalXY &other) const + { + return (decal == other.decal && x == other.x && y == other.y); + } }; struct BelPin 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, QWidget *parent connect(designview, SIGNAL(selected(std::vector)), fpgaView, SLOT(onSelectedArchItem(std::vector))); connect(fpgaView, SIGNAL(clickedBel(BelId)), designview, SLOT(onClickedBel(BelId))); + connect(fpgaView, SIGNAL(clickedWire(WireId)), designview, SLOT(onClickedWire(WireId))); connect(designview, SIGNAL(highlight(std::vector, int)), fpgaView, SLOT(onHighlightGroupChanged(std::vector, 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(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 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> belDecals; - std::vector> wireDecals; - std::vector> pipDecals; - std::vector> groupDecals; + std::vector> belDecals; + std::vector> wireDecals; + std::vector> pipDecals; + std::vector> 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 selectedDecals; + DecalXY hoveredDecal; std::vector 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(new QuadTreeElements(bb)); - - //for (auto const &decal : belDecals) { - // populateQuadTree(data.get(), decal.first, decal.second); - //} + data->qt = std::unique_ptr(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 decals) { QMutexLocker locker(&rendererArgsLock_); rendererArgs_->selectedDecals = decals; - rendererArgs_->highlightedOrSelectedChanged = true; + rendererArgs_->changed = true; } pokeRenderer(); } @@ -374,33 +493,103 @@ void FPGAViewWidget::onHighlightGroupChanged(std::vector 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::pickElement(float worldx, float worldy) +{ + // Get elements from renderer whose BBs correspond to the pick. + std::vector 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; + std::vector 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 #include #include #include @@ -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>; + 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; 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 qt; + std::unique_ptr qt; }; std::unique_ptr rendererData_; QMutex rendererDataLock_; @@ -167,7 +213,8 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions { std::vector selectedDecals; std::vector highlightedDecals[8]; - bool highlightedOrSelectedChanged; + DecalXY hoveredDecal; + bool changed; }; std::unique_ptr 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 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); }; -- cgit v1.2.3 From 402be30268bbd6930c9f76547f8dab3e304005c3 Mon Sep 17 00:00:00 2001 From: Sergiusz Bazanski Date: Thu, 26 Jul 2018 22:41:10 +0100 Subject: gui: after review of quadtree by msgctl, tougher tests --- gui/quadtree.h | 20 +++++++++++++++----- tests/gui/quadtree.cc | 6 +++--- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/gui/quadtree.h b/gui/quadtree.h index 2b5a9df5..f803f770 100644 --- a/gui/quadtree.h +++ b/gui/quadtree.h @@ -61,6 +61,15 @@ class QuadTreeNode return false; return true; } + + // Sort the bounding box coordinates. + void fixup() + { + if (x1_ < x0_) + std::swap(x0_, x1_); + if (y1_ < y0_) + std::swap(y0_, y1_); + } }; private: @@ -281,7 +290,7 @@ class QuadTreeNode // Return count of BoundingBoxes/Elements contained. // @returns count of elements contained. - size_t size(void) const + size_t size() const { size_t res = elems_.size(); if (children_ != nullptr) { @@ -351,20 +360,21 @@ class QuadTree // @param k Bounding box at which to store value. // @param v Value at a given bounding box. // @returns Whether the insert was succesful. - bool insert(const BoundingBox &k, ElementT v) + bool insert(BoundingBox k, ElementT v) { + k.fixup(); return root_.insert(k, v); } // Dump a human-readable representation of the tree to stdout. - void dump(void) const + void dump() const { root_.dump(0); } // Return count of BoundingBoxes/Elements contained. // @returns count of elements contained. - size_t size(void) const + size_t size() const { return root_.size(); } @@ -378,7 +388,7 @@ class QuadTree { std::vector res; root_.get(x, y, res); - return std::move(res); + return res; } }; diff --git a/tests/gui/quadtree.cc b/tests/gui/quadtree.cc index ca90a426..083a0057 100644 --- a/tests/gui/quadtree.cc +++ b/tests/gui/quadtree.cc @@ -58,7 +58,7 @@ TEST_F(QuadTreeTest, insert_count) auto rng = NEXTPNR_NAMESPACE::DeterministicRNG(); // Add 10000 random rectangles. - for (int i = 0; i < 10000; i++) { + for (unsigned int i = 0; i < 10000; i++) { int x0 = rng.rng(width_); int y0 = rng.rng(height_); int w = rng.rng(width_ - x0); @@ -69,7 +69,7 @@ TEST_F(QuadTreeTest, insert_count) ASSERT_EQ(qt_->size(), i+1); } // Add 100000 random points. - for (int i = 0; i < 100000; i++) { + for (unsigned int i = 0; i < 100000; i++) { int x0 = rng.rng(width_); int y0 = rng.rng(height_); int x1 = x0; @@ -113,7 +113,7 @@ TEST_F(QuadTreeTest, insert_retrieve_same) auto res = qt_->get(x, y); // Somewhat arbirary test to make sure we don't return obscene // amounts of data. - ASSERT_LT(res.size(), 200); + ASSERT_LT(res.size(), 200UL); bool found = false; for (auto elem : res) { // Is this what we're looking for? -- cgit v1.2.3 From 48713be0eb8876fdb1ddfce323d59efbac2c8785 Mon Sep 17 00:00:00 2001 From: Sergiusz Bazanski Date: Thu, 26 Jul 2018 22:47:04 +0100 Subject: placer: only lock/unlock at beginning, yield elsewhere --- common/placer1.cc | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/common/placer1.cc b/common/placer1.cc index 4659da11..ee7225b5 100644 --- a/common/placer1.cc +++ b/common/placer1.cc @@ -75,10 +75,10 @@ class SAPlacer bool place() { log_break(); + ctx->lock(); size_t placed_cells = 0; // Initial constraints placer - ctx->lock(); for (auto &cell_entry : ctx->cells) { CellInfo *cell = cell_entry.second.get(); auto loc = cell->attrs.find(ctx->id("BEL")); @@ -112,6 +112,7 @@ class SAPlacer } int constr_placed_cells = placed_cells; log_info("Placed %d cells based on constraints.\n", int(placed_cells)); + ctx->yield(); // Sort to-place cells for deterministic initial placement std::vector autoplaced; @@ -123,28 +124,25 @@ class SAPlacer } std::sort(autoplaced.begin(), autoplaced.end(), [](CellInfo *a, CellInfo *b) { return a->name < b->name; }); ctx->shuffle(autoplaced); - ctx->unlock(); // Place cells randomly initially log_info("Creating initial placement for remaining %d cells.\n", int(autoplaced.size())); for (auto cell : autoplaced) { - ctx->lock(); place_initial(cell); placed_cells++; if ((placed_cells - constr_placed_cells) % 500 == 0) log_info(" initial placement placed %d/%d cells\n", int(placed_cells - constr_placed_cells), int(autoplaced.size())); - ctx->unlock(); } if ((placed_cells - constr_placed_cells) % 500 != 0) log_info(" initial placement placed %d/%d cells\n", int(placed_cells - constr_placed_cells), int(autoplaced.size())); + ctx->yield(); log_info("Running simulated annealing placer.\n"); // Calculate metric after initial placement - ctx->lock(); curr_metric = 0; curr_tns = 0; for (auto &net : ctx->nets) { @@ -152,7 +150,6 @@ class SAPlacer metrics[net.first] = wl; curr_metric += wl; } - ctx->unlock(); int n_no_progress = 0; double avg_metric = curr_metric; @@ -249,7 +246,7 @@ class SAPlacer ctx->yield(); } // Final post-pacement validitiy check - ctx->lock(); + ctx->yield(); for (auto bel : ctx->getBels()) { IdString cell = ctx->getBoundBelCell(bel); if (!ctx->isBelLocationValid(bel)) { -- cgit v1.2.3 From 0eb40da749d22a8bb74306d38c090a85258015e9 Mon Sep 17 00:00:00 2001 From: Sergiusz Bazanski Date: Fri, 27 Jul 2018 01:21:50 +0100 Subject: gui: implement zoom to outbounds --- gui/fpgaviewwidget.cc | 106 ++++++++++++++++++++++++++++++++++++++++---------- gui/fpgaviewwidget.h | 57 ++++++++++++++++++++------- 2 files changed, 128 insertions(+), 35 deletions(-) diff --git a/gui/fpgaviewwidget.cc b/gui/fpgaviewwidget.cc index 46008ece..7e7b6e4f 100644 --- a/gui/fpgaviewwidget.cc +++ b/gui/fpgaviewwidget.cc @@ -33,9 +33,9 @@ NEXTPNR_NAMESPACE_BEGIN FPGAViewWidget::FPGAViewWidget(QWidget *parent) : QOpenGLWidget(parent), ctx_(nullptr), paintTimer_(this), - lineShader_(this), zoom_(500.0f), - rendererData_(new FPGAViewWidget::RendererData), - rendererArgs_(new FPGAViewWidget::RendererArgs) + lineShader_(this), zoom_(10.0f), + rendererArgs_(new FPGAViewWidget::RendererArgs), + rendererData_(new FPGAViewWidget::RendererData) { colors_.background = QColor("#000000"); colors_.grid = QColor("#333"); @@ -55,6 +55,7 @@ FPGAViewWidget::FPGAViewWidget(QWidget *parent) : colors_.highlight[7] = QColor("#da70d6"); rendererArgs_->changed = false; + rendererArgs_->flags.zoomOutbound = true; auto fmt = format(); fmt.setMajorVersion(3); @@ -87,6 +88,10 @@ void FPGAViewWidget::newContext(Context *ctx) onSelectedArchItem(std::vector()); for (int i = 0; i < 8; i++) onHighlightGroupChanged(std::vector(), i); + { + QMutexLocker lock(&rendererArgsLock_); + rendererArgs_->flags.zoomOutbound = true; + } pokeRenderer(); } @@ -258,8 +263,7 @@ QMatrix4x4 FPGAViewWidget::getProjection(void) QMatrix4x4 matrix; const float aspect = float(width()) / float(height()); - matrix.perspective(3.14 / 2, aspect, zoomNear_, zoomFar_); - matrix.translate(0.0f, 0.0f, -zoom_); + matrix.perspective(90, aspect, zoomNear_, zoomFar_); return matrix; } @@ -271,6 +275,7 @@ void FPGAViewWidget::paintGL() gl->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); QMatrix4x4 matrix = getProjection(); + matrix.translate(0.0f, 0.0f, -zoom_); matrix *= viewMove_; @@ -285,6 +290,8 @@ void FPGAViewWidget::paintGL() PolyLine(-100.0f, i, 100.0f, i).build(grid); PolyLine(i, -100.0f, i, 100.0f).build(grid); } + // Flags from pipeline. + PassthroughFlags flags; // Draw grid. lineShader_.draw(grid, colors_.grid, thick1Px, matrix); @@ -303,6 +310,18 @@ void FPGAViewWidget::paintGL() lineShader_.draw(rendererData_->gfxSelected, colors_.selected, thick11Px, matrix); lineShader_.draw(rendererData_->gfxHovered, colors_.hovered, thick2Px, matrix); + + flags = rendererData_->flags; + } + + { + QMutexLocker locker(&rendererArgsLock_); + rendererArgs_->flags.clear(); + } + + // Check flags passed through pipeline. + if (flags.zoomOutbound) { + zoomOutbound(); } } @@ -374,15 +393,21 @@ void FPGAViewWidget::renderLines(void) DecalXY hoveredDecal; std::vector highlightedDecals[8]; bool highlightedOrSelectedChanged; + PassthroughFlags flags; { // 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_->changed; rendererArgs_->changed = false; + + flags = rendererArgs_->flags; } @@ -444,7 +469,7 @@ void FPGAViewWidget::renderLines(void) for (int i = 0; i < 8; i++) data->gfxHighlighted[i] = rendererData_->gfxHighlighted[i]; } - + rendererData_ = std::move(data); } } @@ -476,6 +501,11 @@ void FPGAViewWidget::renderLines(void) } } } + + { + QMutexLocker locker(&rendererDataLock_); + rendererData_->flags = flags; + } } void FPGAViewWidget::onSelectedArchItem(std::vector decals) @@ -601,20 +631,29 @@ QVector4D FPGAViewWidget::mouseToWorldCoordinates(int x, int y) QMatrix4x4 vp; vp.viewport(0, 0, width() * retinaScale, height() * retinaScale); - QVector4D vec(x, y, 0, 1); + QVector4D vec(x, y, 1, 1); vec = vp.inverted() * vec; - vec = projection.inverted() * vec; + vec = projection.inverted() * QVector4D(vec.x(), vec.y(), -1, 1); + + // Hic sunt dracones. + // TODO(q3k): grab a book, remind yourselfl linear algebra and undo this + // operation properly. + QVector3D ray = vec.toVector3DAffine(); + ray.normalize(); + ray.setX((ray.x()/-ray.z()) * zoom_); + ray.setY((ray.y()/ray.z()) * zoom_); + ray.setZ(1.0); - auto ray = vec.toVector3DAffine(); - auto world = QVector4D(ray.x()*ray.z(), -ray.y()*ray.z(), 0, 1); - world = viewMove_.inverted() * world; + vec = viewMove_.inverted() * QVector4D(ray.x(), ray.y(), ray.z(), 1.0); + vec.setZ(0); - return world; + return vec; } QVector4D FPGAViewWidget::mouseToWorldDimensions(float x, float y) { QMatrix4x4 p = getProjection(); + p.translate(0.0f, 0.0f, -zoom_); QVector2D unit = p.map(QVector4D(1, 1, 0, 1)).toVector2DAffine(); float sx = (((float)x) / (width() / 2)); @@ -632,17 +671,19 @@ void FPGAViewWidget::wheelEvent(QWheelEvent *event) void FPGAViewWidget::zoom(int level) { - if (zoom_ < zoomNear_) { - zoom_ = zoomNear_; - } else if (zoom_ < zoomLvl1_) { - zoom_ -= level / 10.0; + if (zoom_ < zoomLvl1_) { + zoom_ -= level / 500.0; } else if (zoom_ < zoomLvl2_) { - zoom_ -= level / 5.0; - } else if (zoom_ < zoomFar_) { - zoom_ -= level; + zoom_ -= level / 100.0; } else { - zoom_ = zoomFar_; + zoom_ -= level / 10.0; + } + + if (zoom_ < zoomNear_) + zoom_ = zoomNear_; + else if (zoom_ > zoomFar_) + zoom_ = zoomFar_; update(); } @@ -652,6 +693,29 @@ void FPGAViewWidget::zoomOut() { zoom(-10); } void FPGAViewWidget::zoomSelected() {} -void FPGAViewWidget::zoomOutbound() {} +void FPGAViewWidget::zoomOutbound() +{ + // Get design bounding box. + float x0, y0, x1, y1; + { + QMutexLocker lock(&rendererDataLock_); + x0 = rendererData_->bbX0; + y0 = rendererData_->bbY0; + x1 = rendererData_->bbX1; + y1 = rendererData_->bbY1; + } + float w = x1 - x0; + float h = y1 - y0; + + viewMove_.setToIdentity(); + viewMove_.translate(-w/2, -h/2); + + // Our FOV is π/2, so distance for camera to see a plane of width H is H/2. + // We add 1 unit to cover half a unit of extra space around. + float distance_w = w/2 + 1; + float distance_h = h/2 + 1; + zoom_ = std::max(distance_w, distance_h); + update(); +} NEXTPNR_NAMESPACE_END diff --git a/gui/fpgaviewwidget.h b/gui/fpgaviewwidget.h index b1eda33a..067f3b7f 100644 --- a/gui/fpgaviewwidget.h +++ b/gui/fpgaviewwidget.h @@ -124,10 +124,10 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions void clickedWire(WireId wire); private: - const float zoomNear_ = 1.0f; // do not zoom closer than this - const float zoomFar_ = 10000.0f; // do not zoom further than this - const float zoomLvl1_ = 100.0f; - const float zoomLvl2_ = 50.0f; + const float zoomNear_ = 0.1f; // do not zoom closer than this + const float zoomFar_ = 100.0f; // do not zoom further than this + const float zoomLvl1_ = 1.0f; + const float zoomLvl2_ = 5.0f; struct PickedElement { ElementType type; @@ -195,6 +195,43 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions QColor highlight[8]; } colors_; + // Flags that are passed through from renderer arguments to renderer data. + // These are used by the UI code to signal events that will only fire when + // the next frame gets rendered. + struct PassthroughFlags + { + bool zoomOutbound; + + PassthroughFlags() : + zoomOutbound(false) {} + PassthroughFlags &operator=(const PassthroughFlags &other) noexcept { + zoomOutbound = other.zoomOutbound; + return *this; + } + + void clear() + { + zoomOutbound = false; + } + }; + + struct RendererArgs + { + // Decals that he user selected. + std::vector selectedDecals; + // Decals that the user highlighted. + std::vector highlightedDecals[8]; + // Decals that the user's mouse is hovering in. + DecalXY hoveredDecal; + // Whether to render the above three or skip it. + bool changed; + + // Flags to pass back into the RendererData. + PassthroughFlags flags; + }; + std::unique_ptr rendererArgs_; + QMutex rendererArgsLock_; + struct RendererData { LineShaderData gfxByStyle[GraphicElement::STYLE_MAX]; @@ -205,20 +242,12 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions float bbX0, bbY0, bbX1, bbY1; // Quadtree for picking objects. std::unique_ptr qt; + // Flags from args. + PassthroughFlags flags; }; std::unique_ptr rendererData_; QMutex rendererDataLock_; - struct RendererArgs - { - std::vector selectedDecals; - std::vector highlightedDecals[8]; - DecalXY hoveredDecal; - bool changed; - }; - std::unique_ptr rendererArgs_; - QMutex rendererArgsLock_; - void zoom(int level); void renderLines(void); void renderGraphicElement(RendererData *data, LineShaderData &out, const GraphicElement &el, float x, float y); -- cgit v1.2.3 From 5a7fe84a042439d83f152661c58c9d5fa8ed8e52 Mon Sep 17 00:00:00 2001 From: Sergiusz Bazanski Date: Fri, 27 Jul 2018 01:22:29 +0100 Subject: gui: clang-format --- gui/fpgaviewwidget.cc | 131 +++++++++++++++++++++++++------------------------- gui/fpgaviewwidget.h | 28 +++++------ gui/lineshader.cc | 2 +- gui/quadtree.h | 111 +++++++++++++++++++++--------------------- 4 files changed, 135 insertions(+), 137 deletions(-) diff --git a/gui/fpgaviewwidget.cc b/gui/fpgaviewwidget.cc index 7e7b6e4f..ac51467c 100644 --- a/gui/fpgaviewwidget.cc +++ b/gui/fpgaviewwidget.cc @@ -31,11 +31,9 @@ NEXTPNR_NAMESPACE_BEGIN -FPGAViewWidget::FPGAViewWidget(QWidget *parent) : - QOpenGLWidget(parent), ctx_(nullptr), paintTimer_(this), - lineShader_(this), zoom_(10.0f), - rendererArgs_(new FPGAViewWidget::RendererArgs), - rendererData_(new FPGAViewWidget::RendererData) +FPGAViewWidget::FPGAViewWidget(QWidget *parent) + : QOpenGLWidget(parent), ctx_(nullptr), paintTimer_(this), lineShader_(this), zoom_(10.0f), + rendererArgs_(new FPGAViewWidget::RendererArgs), rendererData_(new FPGAViewWidget::RendererData) { colors_.background = QColor("#000000"); colors_.grid = QColor("#333"); @@ -126,52 +124,54 @@ float FPGAViewWidget::PickedElement::distance(Context *ctx, float wx, float wy) // Go over its' GraphicElements, and calculate the distance to them. std::vector 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; - } - }); + 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; + 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) +void FPGAViewWidget::renderGraphicElement(RendererData *data, LineShaderData &out, const GraphicElement &el, float x, + float y) { if (el.type == GraphicElement::TYPE_BOX) { auto line = PolyLine(true); @@ -234,15 +234,15 @@ void FPGAViewWidget::populateQuadTree(RendererData *data, const DecalXY &decal, 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); + 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; + 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) @@ -301,7 +301,8 @@ void FPGAViewWidget::paintGL() // Render Arch graphics. lineShader_.draw(rendererData_->gfxByStyle[GraphicElement::STYLE_FRAME], colors_.frame, thick11Px, matrix); lineShader_.draw(rendererData_->gfxByStyle[GraphicElement::STYLE_HIDDEN], colors_.hidden, thick11Px, matrix); - lineShader_.draw(rendererData_->gfxByStyle[GraphicElement::STYLE_INACTIVE], colors_.inactive, thick11Px, matrix); + lineShader_.draw(rendererData_->gfxByStyle[GraphicElement::STYLE_INACTIVE], colors_.inactive, thick11Px, + matrix); lineShader_.draw(rendererData_->gfxByStyle[GraphicElement::STYLE_ACTIVE], colors_.active, thick11Px, matrix); // Draw highlighted items. @@ -313,7 +314,7 @@ void FPGAViewWidget::paintGL() flags = rendererData_->flags; } - + { QMutexLocker locker(&rendererArgsLock_); rendererArgs_->flags.clear(); @@ -410,7 +411,6 @@ void FPGAViewWidget::renderLines(void) flags = rendererArgs_->flags; } - // Render decals if necessary. if (decalsChanged) { auto data = std::unique_ptr(new FPGAViewWidget::RendererData); @@ -469,7 +469,7 @@ void FPGAViewWidget::renderLines(void) for (int i = 0; i < 8; i++) data->gfxHighlighted[i] = rendererData_->gfxHighlighted[i]; } - + rendererData_ = std::move(data); } } @@ -549,15 +549,16 @@ boost::optional FPGAViewWidget::pickElement(float // Calculate distances to all elements picked. using ElemDist = std::pair; std::vector 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)); - }); + 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; + 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; }); @@ -619,7 +620,6 @@ void FPGAViewWidget::mouseMoveEvent(QMouseEvent *event) update(); } - // Invert the projection matrix to calculate screen/mouse to world/grid // coordinates. QVector4D FPGAViewWidget::mouseToWorldCoordinates(int x, int y) @@ -640,8 +640,8 @@ QVector4D FPGAViewWidget::mouseToWorldCoordinates(int x, int y) // operation properly. QVector3D ray = vec.toVector3DAffine(); ray.normalize(); - ray.setX((ray.x()/-ray.z()) * zoom_); - ray.setY((ray.y()/ray.z()) * zoom_); + ray.setX((ray.x() / -ray.z()) * zoom_); + ray.setY((ray.y() / ray.z()) * zoom_); ray.setZ(1.0); vec = viewMove_.inverted() * QVector4D(ray.x(), ray.y(), ray.z(), 1.0); @@ -677,7 +677,6 @@ void FPGAViewWidget::zoom(int level) zoom_ -= level / 100.0; } else { zoom_ -= level / 10.0; - } if (zoom_ < zoomNear_) @@ -708,12 +707,12 @@ void FPGAViewWidget::zoomOutbound() float h = y1 - y0; viewMove_.setToIdentity(); - viewMove_.translate(-w/2, -h/2); + viewMove_.translate(-w / 2, -h / 2); // Our FOV is π/2, so distance for camera to see a plane of width H is H/2. // We add 1 unit to cover half a unit of extra space around. - float distance_w = w/2 + 1; - float distance_h = h/2 + 1; + float distance_w = w / 2 + 1; + float distance_h = h / 2 + 1; zoom_ = std::max(distance_w, distance_h); update(); } diff --git a/gui/fpgaviewwidget.h b/gui/fpgaviewwidget.h index 067f3b7f..4fefe020 100644 --- a/gui/fpgaviewwidget.h +++ b/gui/fpgaviewwidget.h @@ -20,7 +20,6 @@ #ifndef MAPGLWIDGET_H #define MAPGLWIDGET_H -#include #include #include #include @@ -32,11 +31,12 @@ #include #include #include +#include +#include "designwidget.h" +#include "lineshader.h" #include "nextpnr.h" #include "quadtree.h" -#include "lineshader.h" -#include "designwidget.h" NEXTPNR_NAMESPACE_BEGIN @@ -108,7 +108,6 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions QSize minimumSizeHint() const override; QSize sizeHint() const override; - public Q_SLOTS: void newContext(Context *ctx); void onSelectedArchItem(std::vector decals); @@ -124,14 +123,16 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions void clickedWire(WireId wire); private: - const float zoomNear_ = 0.1f; // do not zoom closer than this + const float zoomNear_ = 0.1f; // do not zoom closer than this const float zoomFar_ = 100.0f; // do not zoom further than this const float zoomLvl1_ = 1.0f; const float zoomLvl2_ = 5.0f; - struct PickedElement { + struct PickedElement + { ElementType type; - union Inner { + union Inner + { BelId bel; WireId wire; PipId pip; @@ -202,17 +203,14 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions { bool zoomOutbound; - PassthroughFlags() : - zoomOutbound(false) {} - PassthroughFlags &operator=(const PassthroughFlags &other) noexcept { + PassthroughFlags() : zoomOutbound(false) {} + PassthroughFlags &operator=(const PassthroughFlags &other) noexcept + { zoomOutbound = other.zoomOutbound; return *this; } - void clear() - { - zoomOutbound = false; - } + void clear() { zoomOutbound = false; } }; struct RendererArgs @@ -253,7 +251,7 @@ 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, const PickedElement& element); + void populateQuadTree(RendererData *data, const DecalXY &decal, const PickedElement &element); boost::optional pickElement(float worldx, float worldy); QVector4D mouseToWorldCoordinates(int x, int y); QVector4D mouseToWorldDimensions(float x, float y); diff --git a/gui/lineshader.cc b/gui/lineshader.cc index 94a7a010..eba96020 100644 --- a/gui/lineshader.cc +++ b/gui/lineshader.cc @@ -17,8 +17,8 @@ * */ -#include "log.h" #include "lineshader.h" +#include "log.h" NEXTPNR_NAMESPACE_BEGIN diff --git a/gui/quadtree.h b/gui/quadtree.h index f803f770..be677747 100644 --- a/gui/quadtree.h +++ b/gui/quadtree.h @@ -26,14 +26,16 @@ NEXTPNR_NAMESPACE_BEGIN // A node of a QuadTree. Internal. -template -class QuadTreeNode +template class QuadTreeNode { public: - class BoundingBox { - friend class QuadTreeNode; + class BoundingBox + { + friend class QuadTreeNode; + private: CoordinateT x0_, x1_, y0_, y1_; + public: // Standard constructor for a given (x0,y0), (x1,y1) bounding box // @@ -41,11 +43,11 @@ class QuadTreeNode // @param y0 y coordinate of top-left corner of box // @param x1 x coordinate of bottom-right corner of box // @param y1 y coordinate of bottom-right corner of box - BoundingBox(CoordinateT x0, CoordinateT y0, CoordinateT x1, CoordinateT y1) : - x0_(x0), x1_(x1), y0_(y0), y1_(y1) {} + BoundingBox(CoordinateT x0, CoordinateT y0, CoordinateT x1, CoordinateT y1) : x0_(x0), x1_(x1), y0_(y0), y1_(y1) + { + } - BoundingBox(const BoundingBox &other) : - x0_(other.x0_), x1_(other.x1_), y0_(other.y0_), y1_(other.y1_) {} + BoundingBox(const BoundingBox &other) : x0_(other.x0_), x1_(other.x1_), y0_(other.y0_), y1_(other.y1_) {} // Whether a bounding box contains a given points. // A point is defined to be in a bounding box when it's not lesser than @@ -74,14 +76,16 @@ class QuadTreeNode private: // A pair of Element and BoundingBox that contains it. - class BoundElement { - friend class QuadTreeNode; + class BoundElement + { + friend class QuadTreeNode; + private: BoundingBox bb_; ElementT elem_; + public: - BoundElement(BoundingBox bb, ElementT elem) : - bb_(bb), elem_(elem) {} + BoundElement(BoundingBox bb, ElementT elem) : bb_(bb), elem_(elem) {} }; // The bounding box that this node describes. @@ -103,7 +107,7 @@ class QuadTreeNode // Depth at which this node is - root is at 0, first level at 1, etc. int depth_; - // Checks whether a given bounding box fits within this node - used for + // Checks whether a given bounding box fits within this node - used for // sanity checking on insertion. // @param b bounding box to check // @returns whether b fits in this node entirely @@ -124,7 +128,8 @@ class QuadTreeNode // Used to describe one of 5 possible places an element can exist: // - the node itself (THIS) // - any of the 4 children nodes. - enum Quadrant { + enum Quadrant + { THIS = -1, NW = 0, NE = 1, @@ -175,23 +180,19 @@ class QuadTreeNode return true; } - public: // Standard constructor for node. // @param b BoundingBox this node covers. // @param depth depth at which this node is in the tree // @max_elems how many elements should this node contain before it splits - QuadTreeNode(BoundingBox b, int depth, size_t max_elems = 4) : - bound_(b), max_elems_(max_elems), depth_(depth) - { - } + QuadTreeNode(BoundingBox b, int depth, size_t max_elems = 4) : bound_(b), max_elems_(max_elems), depth_(depth) {} // Disallow copies. QuadTreeNode(const QuadTreeNode &other) = delete; QuadTreeNode &operator=(const QuadTreeNode &other) = delete; // Allow moves. - QuadTreeNode(QuadTreeNode &&other) : - bound_(other.bound_), max_elems_(other.max_elems_), children_(std::move(other.children_)), - splitx_(other.splitx_), splity_(other.splity_), elems_(std::move(other.elems_)), depth_(other.depth_) + QuadTreeNode(QuadTreeNode &&other) + : bound_(other.bound_), max_elems_(other.max_elems_), children_(std::move(other.children_)), + splitx_(other.splitx_), splity_(other.splity_), elems_(std::move(other.elems_)), depth_(other.depth_) { other.children_ = nullptr; } @@ -221,14 +222,14 @@ class QuadTreeNode // Do we have children? if (children_ != nullptr) { - // Put the element either recursively into a child if it fits - // entirely or keep it for ourselves if not. - auto quad = quadrant(k); - if (quad == THIS) { - elems_.push_back(BoundElement(k, std::move(v))); - } else { - return children_[quad].insert(k, std::move(v)); - } + // Put the element either recursively into a child if it fits + // entirely or keep it for ourselves if not. + auto quad = quadrant(k); + if (quad == THIS) { + elems_.push_back(BoundElement(k, std::move(v))); + } else { + return children_[quad].insert(k, std::move(v)); + } } else { // No children and not about to have any. if (!should_split()) { @@ -242,10 +243,17 @@ class QuadTreeNode children_ = decltype(children_)(new QuadTreeNode[4] { // Note: not using [NW] = QuadTreeNode because that seems to // crash g++ 7.3.0. - /* NW */ QuadTreeNode(BoundingBox(bound_.x0_, bound_.y0_, splitx_, splity_), depth_+1, max_elems_), - /* NE */ QuadTreeNode(BoundingBox(splitx_, bound_.y0_, bound_.x1_, splity_), depth_+1, max_elems_), - /* SW */ QuadTreeNode(BoundingBox(bound_.x0_, splity_, splitx_, bound_.y1_), depth_+1, max_elems_), - /* SE */ QuadTreeNode(BoundingBox(splitx_, splity_, bound_.x1_, bound_.y1_), depth_+1, max_elems_), + /* NW */ QuadTreeNode(BoundingBox(bound_.x0_, bound_.y0_, splitx_, splity_), + depth_ + 1, max_elems_), + /* NE */ + QuadTreeNode(BoundingBox(splitx_, bound_.y0_, bound_.x1_, splity_), + depth_ + 1, max_elems_), + /* SW */ + QuadTreeNode(BoundingBox(bound_.x0_, splity_, splitx_, bound_.y1_), + depth_ + 1, max_elems_), + /* SE */ + QuadTreeNode(BoundingBox(splitx_, splity_, bound_.x1_, bound_.y1_), + depth_ + 1, max_elems_), }); // Move all elements to where they belong. auto it = elems_.begin(); @@ -271,20 +279,23 @@ class QuadTreeNode // Dump a human-readable representation of the tree to stdout. void dump(int level) const { - for (int i = 0; i < level; i++) printf(" "); + for (int i = 0; i < level; i++) + printf(" "); printf("loc: % 3d % 3d % 3d % 3d\n", bound_.x0_, bound_.y0_, bound_.x1_, bound_.y1_); if (elems_.size() != 0) { - for (int i = 0; i < level; i++) printf(" "); + for (int i = 0; i < level; i++) + printf(" "); printf("elems: %zu\n", elems_.size()); } if (children_ != nullptr) { - for (int i = 0; i < level; i++) printf(" "); + for (int i = 0; i < level; i++) + printf(" "); printf("children:\n"); - children_[NW].dump(level+1); - children_[NE].dump(level+1); - children_[SW].dump(level+1); - children_[SE].dump(level+1); + children_[NW].dump(level + 1); + children_[NE].dump(level + 1); + children_[SW].dump(level + 1); + children_[SE].dump(level + 1); } } @@ -331,8 +342,7 @@ class QuadTreeNode // // @param CoodinateT scalar type of the coordinate system - int, float, ... // @param ElementT type of the contained element. Must be movable or copiable. -template -class QuadTree +template class QuadTree { private: // Root of the tree. @@ -348,10 +358,7 @@ class QuadTree // // @param b Bounding box of the entire tree - all comitted elements must // fit within in. - QuadTree(BoundingBox b) : - root_(b, 0) - { - } + QuadTree(BoundingBox b) : root_(b, 0) {} // Inserts a new value at a given bounding box.e // BoundingBoxes are not deduplicated - if two are pushed with the same @@ -367,17 +374,11 @@ class QuadTree } // Dump a human-readable representation of the tree to stdout. - void dump() const - { - root_.dump(0); - } + void dump() const { root_.dump(0); } // Return count of BoundingBoxes/Elements contained. // @returns count of elements contained. - size_t size() const - { - return root_.size(); - } + size_t size() const { return root_.size(); } // Retrieve elements whose bounding boxes cover the given coordinates. // -- cgit v1.2.3 From 6db0731ea5a4b27cba43bc105bcd7eb8bae52362 Mon Sep 17 00:00:00 2001 From: Sergiusz Bazanski Date: Fri, 27 Jul 2018 02:04:24 +0100 Subject: gui: implement zoom to selection --- gui/fpgaviewwidget.cc | 89 +++++++++++++++++++++++++++------------------------ gui/fpgaviewwidget.h | 11 ++++--- gui/quadtree.h | 33 +++++++++++++++++-- 3 files changed, 84 insertions(+), 49 deletions(-) diff --git a/gui/fpgaviewwidget.cc b/gui/fpgaviewwidget.cc index ac51467c..873800c5 100644 --- a/gui/fpgaviewwidget.cc +++ b/gui/fpgaviewwidget.cc @@ -170,8 +170,7 @@ float FPGAViewWidget::PickedElement::distance(Context *ctx, float wx, float wy) }); } -void FPGAViewWidget::renderGraphicElement(RendererData *data, LineShaderData &out, const GraphicElement &el, float x, - float y) +void FPGAViewWidget::renderGraphicElement(LineShaderData &out, PickQuadTree::BoundingBox &bb, const GraphicElement &el, float x, float y) { if (el.type == GraphicElement::TYPE_BOX) { auto line = PolyLine(true); @@ -181,30 +180,34 @@ void FPGAViewWidget::renderGraphicElement(RendererData *data, LineShaderData &ou line.point(x + el.x1, y + el.y2); line.build(out); - data->bbX0 = std::min(data->bbX0, x + el.x1); - data->bbY0 = std::min(data->bbY0, x + el.y1); - data->bbX1 = std::max(data->bbX1, x + el.x2); - data->bbY1 = std::max(data->bbY1, x + el.y2); + bb.setX0(std::min(bb.x0(), x + el.x1)); + bb.setY0(std::min(bb.y0(), y + el.y1)); + bb.setX1(std::max(bb.x1(), x + el.x2)); + bb.setY1(std::max(bb.y1(), y + el.y2)); return; } if (el.type == GraphicElement::TYPE_LINE || el.type == GraphicElement::TYPE_ARROW) { PolyLine(x + el.x1, y + el.y1, x + el.x2, y + el.y2).build(out); + bb.setX0(std::min(bb.x0(), x + el.x1)); + bb.setY0(std::min(bb.y0(), y + el.y1)); + bb.setX1(std::max(bb.x1(), x + el.x2)); + bb.setY1(std::max(bb.y1(), y + el.y2)); return; } } -void FPGAViewWidget::renderDecal(RendererData *data, LineShaderData &out, const DecalXY &decal) +void FPGAViewWidget::renderDecal(LineShaderData &out, PickQuadTree::BoundingBox &bb, const DecalXY &decal) { float offsetX = decal.x; float offsetY = decal.y; for (auto &el : ctx_->getDecalGraphics(decal.decal)) { - renderGraphicElement(data, out, el, offsetX, offsetY); + renderGraphicElement(out, bb, el, offsetX, offsetY); } } -void FPGAViewWidget::renderArchDecal(RendererData *data, const DecalXY &decal) +void FPGAViewWidget::renderArchDecal(LineShaderData out[GraphicElement::STYLE_MAX], PickQuadTree::BoundingBox &bb, const DecalXY &decal) { float offsetX = decal.x; float offsetY = decal.y; @@ -214,7 +217,7 @@ void FPGAViewWidget::renderArchDecal(RendererData *data, const DecalXY &decal) case GraphicElement::STYLE_FRAME: case GraphicElement::STYLE_INACTIVE: case GraphicElement::STYLE_ACTIVE: - renderGraphicElement(data, data->gfxByStyle[el.style], el, offsetX, offsetY); + renderGraphicElement(out[el.style], bb, el, offsetX, offsetY); break; default: break; @@ -415,35 +418,31 @@ void FPGAViewWidget::renderLines(void) if (decalsChanged) { auto data = std::unique_ptr(new FPGAViewWidget::RendererData); // Reset bounding box. - data->bbX0 = 0; - data->bbY0 = 0; - data->bbX1 = 0; - data->bbY1 = 0; + data->bbGlobal.clear(); // Draw Bels. for (auto const &decal : belDecals) { - renderArchDecal(data.get(), decal.first); + renderArchDecal(data->gfxByStyle, data->bbGlobal, decal.first); } // Draw Wires. for (auto const &decal : wireDecals) { - renderArchDecal(data.get(), decal.first); + renderArchDecal(data->gfxByStyle, data->bbGlobal, decal.first); } // Draw Pips. for (auto const &decal : pipDecals) { - renderArchDecal(data.get(), decal.first); + renderArchDecal(data->gfxByStyle, data->bbGlobal, decal.first); } // Draw Groups. for (auto const &decal : groupDecals) { - renderArchDecal(data.get(), decal.first); + renderArchDecal(data->gfxByStyle, data->bbGlobal, decal.first); } // Bounding box should be calculated by now. - NPNR_ASSERT((data->bbX1 - data->bbX0) != 0); - NPNR_ASSERT((data->bbY1 - data->bbY0) != 0); - auto bb = PickQuadTree::BoundingBox(data->bbX0, data->bbY0, data->bbX1, data->bbY1); + NPNR_ASSERT(data->bbGlobal.w() != 0); + NPNR_ASSERT(data->bbGlobal.h() != 0); // Populate picking quadtree. - data->qt = std::unique_ptr(new PickQuadTree(bb)); + data->qt = std::unique_ptr(new PickQuadTree(data->bbGlobal)); for (auto const &decal : belDecals) { populateQuadTree(data.get(), decal.first, PickedElement(decal.second, decal.first.x, decal.first.y)); } @@ -480,24 +479,25 @@ void FPGAViewWidget::renderLines(void) // Whether the currently being hovered decal is also selected. bool hoveringSelected = false; // Render selected. + rendererData_->bbSelected.clear(); rendererData_->gfxSelected.clear(); for (auto &decal : selectedDecals) { if (decal == hoveredDecal) hoveringSelected = true; - renderDecal(rendererData_.get(), rendererData_->gfxSelected, decal); + renderDecal(rendererData_->gfxSelected, rendererData_->bbSelected, decal); } // Render hovered. rendererData_->gfxHovered.clear(); if (!hoveringSelected) { - renderDecal(rendererData_.get(), rendererData_->gfxHovered, hoveredDecal); + renderDecal(rendererData_->gfxHovered, rendererData_->bbGlobal, hoveredDecal); } // Render highlighted. for (int i = 0; i < 8; i++) { rendererData_->gfxHighlighted[i].clear(); for (auto &decal : highlightedDecals[i]) { - renderDecal(rendererData_.get(), rendererData_->gfxHighlighted[i], decal); + renderDecal(rendererData_->gfxHighlighted[i], rendererData_->bbGlobal, decal); } } } @@ -690,31 +690,36 @@ void FPGAViewWidget::zoomIn() { zoom(10); } void FPGAViewWidget::zoomOut() { zoom(-10); } -void FPGAViewWidget::zoomSelected() {} - -void FPGAViewWidget::zoomOutbound() +void FPGAViewWidget::zoomToBB(const PickQuadTree::BoundingBox &bb) { - // Get design bounding box. - float x0, y0, x1, y1; - { - QMutexLocker lock(&rendererDataLock_); - x0 = rendererData_->bbX0; - y0 = rendererData_->bbY0; - x1 = rendererData_->bbX1; - y1 = rendererData_->bbY1; - } - float w = x1 - x0; - float h = y1 - y0; + if (bb.w() < 0.00005 && bb.h() < 0.00005) + return; viewMove_.setToIdentity(); - viewMove_.translate(-w / 2, -h / 2); + viewMove_.translate(-(bb.x0() + bb.w() / 2), -(bb.y0() + bb.h() / 2)); // Our FOV is π/2, so distance for camera to see a plane of width H is H/2. // We add 1 unit to cover half a unit of extra space around. - float distance_w = w / 2 + 1; - float distance_h = h / 2 + 1; + float distance_w = bb.w() / 2 + 1; + float distance_h = bb.h() / 2 + 1; zoom_ = std::max(distance_w, distance_h); +} + +void FPGAViewWidget::zoomSelected() +{ + { + QMutexLocker lock(&rendererDataLock_); + zoomToBB(rendererData_->bbSelected); + } update(); } +void FPGAViewWidget::zoomOutbound() +{ + { + QMutexLocker lock(&rendererDataLock_); + zoomToBB(rendererData_->bbGlobal); + } +} + NEXTPNR_NAMESPACE_END diff --git a/gui/fpgaviewwidget.h b/gui/fpgaviewwidget.h index 4fefe020..a3593bd4 100644 --- a/gui/fpgaviewwidget.h +++ b/gui/fpgaviewwidget.h @@ -237,7 +237,9 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions LineShaderData gfxHovered; LineShaderData gfxHighlighted[8]; // Global bounding box of data from Arch. - float bbX0, bbY0, bbX1, bbY1; + PickQuadTree::BoundingBox bbGlobal; + // Bounding box of selected items. + PickQuadTree::BoundingBox bbSelected; // Quadtree for picking objects. std::unique_ptr qt; // Flags from args. @@ -246,11 +248,12 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions std::unique_ptr rendererData_; QMutex rendererDataLock_; + void zoomToBB(const PickQuadTree::BoundingBox &bb); void zoom(int level); void renderLines(void); - 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 renderGraphicElement(LineShaderData &out, PickQuadTree::BoundingBox &bb, const GraphicElement &el, float x, float y); + void renderDecal(LineShaderData &out, PickQuadTree::BoundingBox &bb, const DecalXY &decal); + void renderArchDecal(LineShaderData out[GraphicElement::STYLE_MAX], PickQuadTree::BoundingBox &bb, const DecalXY &decal); void populateQuadTree(RendererData *data, const DecalXY &decal, const PickedElement &element); boost::optional pickElement(float worldx, float worldy); QVector4D mouseToWorldCoordinates(int x, int y); diff --git a/gui/quadtree.h b/gui/quadtree.h index be677747..22065c2f 100644 --- a/gui/quadtree.h +++ b/gui/quadtree.h @@ -34,7 +34,10 @@ template class QuadTreeNode friend class QuadTreeNode; private: - CoordinateT x0_, x1_, y0_, y1_; + CoordinateT x0_, y0_, x1_, y1_; + + static constexpr float pinf = std::numeric_limits::infinity(); + static constexpr float ninf = -std::numeric_limits::infinity(); public: // Standard constructor for a given (x0,y0), (x1,y1) bounding box @@ -43,11 +46,14 @@ template class QuadTreeNode // @param y0 y coordinate of top-left corner of box // @param x1 x coordinate of bottom-right corner of box // @param y1 y coordinate of bottom-right corner of box - BoundingBox(CoordinateT x0, CoordinateT y0, CoordinateT x1, CoordinateT y1) : x0_(x0), x1_(x1), y0_(y0), y1_(y1) + BoundingBox(CoordinateT x0, CoordinateT y0, CoordinateT x1, CoordinateT y1) : x0_(x0), y0_(y0), x1_(x1), y1_(y1) { } - BoundingBox(const BoundingBox &other) : x0_(other.x0_), x1_(other.x1_), y0_(other.y0_), y1_(other.y1_) {} + + BoundingBox() : x0_(pinf), y0_(pinf), x1_(ninf), y1_(ninf) {} + + BoundingBox(const BoundingBox &other) : x0_(other.x0_), y0_(other.y0_), x1_(other.x1_), y1_(other.y1_) {} // Whether a bounding box contains a given points. // A point is defined to be in a bounding box when it's not lesser than @@ -72,6 +78,27 @@ template class QuadTreeNode if (y1_ < y0_) std::swap(y0_, y1_); } + + CoordinateT x0() const { return x0_; } + CoordinateT y0() const { return y0_; } + CoordinateT x1() const { return x1_; } + CoordinateT y1() const { return y1_; } + + void setX0(CoordinateT v) { x0_ = v; } + void setY0(CoordinateT v) { y0_ = v; } + void setX1(CoordinateT v) { x1_ = v; } + void setY1(CoordinateT v) { y1_ = v; } + + void clear() + { + x0_ = pinf; + y0_ = pinf; + x1_ = ninf; + y1_ = ninf; + } + + CoordinateT w() const { return x1_ - x0_; } + CoordinateT h() const { return y1_ - y0_; } }; private: -- cgit v1.2.3 From 83371248fca5ab217def81ee101ecdb0d299cee4 Mon Sep 17 00:00:00 2001 From: Sergiusz Bazanski Date: Fri, 27 Jul 2018 02:06:33 +0100 Subject: gui: rejigger maximum zoom out --- gui/fpgaviewwidget.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/fpgaviewwidget.h b/gui/fpgaviewwidget.h index a3593bd4..732f4e3e 100644 --- a/gui/fpgaviewwidget.h +++ b/gui/fpgaviewwidget.h @@ -124,7 +124,7 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions private: const float zoomNear_ = 0.1f; // do not zoom closer than this - const float zoomFar_ = 100.0f; // do not zoom further than this + const float zoomFar_ = 30.0f; // do not zoom further than this const float zoomLvl1_ = 1.0f; const float zoomLvl2_ = 5.0f; -- cgit v1.2.3 From 1fe1b99a5afea488a801f303cb1eda0d9343a1f0 Mon Sep 17 00:00:00 2001 From: Sergiusz Bazanski Date: Fri, 27 Jul 2018 02:14:40 +0100 Subject: gui: ctrl click to select multiple elements --- gui/basewindow.cc | 8 ++++---- gui/designwidget.cc | 14 +++++++------- gui/designwidget.h | 6 +++--- gui/fpgaviewwidget.cc | 16 +++++++++++----- gui/fpgaviewwidget.h | 6 +++--- 5 files changed, 28 insertions(+), 22 deletions(-) diff --git a/gui/basewindow.cc b/gui/basewindow.cc index c7e637f6..96799e85 100644 --- a/gui/basewindow.cc +++ b/gui/basewindow.cc @@ -80,10 +80,10 @@ BaseMainWindow::BaseMainWindow(std::unique_ptr context, QWidget *parent centralTabWidget->tabBar()->tabButton(0, QTabBar::RightSide)->resize(0, 0); connect(this, SIGNAL(contextChanged(Context *)), fpgaView, SLOT(newContext(Context *))); - connect(designview, SIGNAL(selected(std::vector)), fpgaView, - SLOT(onSelectedArchItem(std::vector))); - connect(fpgaView, SIGNAL(clickedBel(BelId)), designview, SLOT(onClickedBel(BelId))); - connect(fpgaView, SIGNAL(clickedWire(WireId)), designview, SLOT(onClickedWire(WireId))); + connect(designview, SIGNAL(selected(std::vector, bool)), fpgaView, + SLOT(onSelectedArchItem(std::vector, bool))); + connect(fpgaView, SIGNAL(clickedBel(BelId, bool)), designview, SLOT(onClickedBel(BelId, bool))); + connect(fpgaView, SIGNAL(clickedWire(WireId, bool)), designview, SLOT(onClickedWire(WireId, bool))); connect(designview, SIGNAL(highlight(std::vector, int)), fpgaView, SLOT(onHighlightGroupChanged(std::vector, int))); diff --git a/gui/designwidget.cc b/gui/designwidget.cc index 34f7a656..674c74c7 100644 --- a/gui/designwidget.cc +++ b/gui/designwidget.cc @@ -507,18 +507,18 @@ QtProperty *DesignWidget::addSubGroup(QtProperty *topItem, const QString &name) return item; } -void DesignWidget::onClickedBel(BelId bel) +void DesignWidget::onClickedBel(BelId bel, bool keep) { QTreeWidgetItem *item = nameToItem[getElementIndex(ElementType::BEL)].value(ctx->getBelName(bel).c_str(ctx)); treeWidget->setCurrentItem(item); - Q_EMIT selected(getDecals(ElementType::BEL, ctx->getBelName(bel))); + Q_EMIT selected(getDecals(ElementType::BEL, ctx->getBelName(bel)), keep); } -void DesignWidget::onClickedWire(WireId wire) +void DesignWidget::onClickedWire(WireId wire, bool keep) { 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))); + Q_EMIT selected(getDecals(ElementType::WIRE, ctx->getWireName(wire)), keep); } void DesignWidget::onItemSelectionChanged() @@ -534,7 +534,7 @@ void DesignWidget::onItemSelectionChanged() std::vector d = getDecals(type, value); std::move(d.begin(), d.end(), std::back_inserter(decals)); } - Q_EMIT selected(decals); + Q_EMIT selected(decals, false); return; } @@ -555,7 +555,7 @@ void DesignWidget::onItemSelectionChanged() clearProperties(); IdString c = static_cast(clickItem)->getData(); - Q_EMIT selected(getDecals(type, c)); + Q_EMIT selected(getDecals(type, c), false); if (type == ElementType::BEL) { BelId bel = ctx->getBelByName(c); @@ -847,7 +847,7 @@ void DesignWidget::prepareMenuProperty(const QPoint &pos) std::vector d = getDecals(type, value); std::move(d.begin(), d.end(), std::back_inserter(decals)); } - Q_EMIT selected(decals); + Q_EMIT selected(decals, false); }); menu.addAction(selectAction); diff --git a/gui/designwidget.h b/gui/designwidget.h index fec0d069..a11ed062 100644 --- a/gui/designwidget.h +++ b/gui/designwidget.h @@ -64,7 +64,7 @@ class DesignWidget : public QWidget void updateHighlightGroup(QList item, int group); Q_SIGNALS: void info(std::string text); - void selected(std::vector decal); + void selected(std::vector decal, bool keep); void highlight(std::vector decal, int group); private Q_SLOTS: @@ -75,8 +75,8 @@ class DesignWidget : public QWidget public Q_SLOTS: void newContext(Context *ctx); void updateTree(); - void onClickedBel(BelId bel); - void onClickedWire(WireId wire); + void onClickedBel(BelId bel, bool keep); + void onClickedWire(WireId wire, bool keep); private: Context *ctx; diff --git a/gui/fpgaviewwidget.cc b/gui/fpgaviewwidget.cc index 873800c5..5d7cb8ed 100644 --- a/gui/fpgaviewwidget.cc +++ b/gui/fpgaviewwidget.cc @@ -83,7 +83,7 @@ FPGAViewWidget::~FPGAViewWidget() {} void FPGAViewWidget::newContext(Context *ctx) { ctx_ = ctx; - onSelectedArchItem(std::vector()); + onSelectedArchItem(std::vector(), false); for (int i = 0; i < 8; i++) onHighlightGroupChanged(std::vector(), i); { @@ -508,11 +508,15 @@ void FPGAViewWidget::renderLines(void) } } -void FPGAViewWidget::onSelectedArchItem(std::vector decals) +void FPGAViewWidget::onSelectedArchItem(std::vector decals, bool keep) { { QMutexLocker locker(&rendererArgsLock_); - rendererArgs_->selectedDecals = decals; + if (keep) { + std::copy(decals.begin(), decals.end(), std::back_inserter(rendererArgs_->selectedDecals)); + } else { + rendererArgs_->selectedDecals = decals; + } rendererArgs_->changed = true; } pokeRenderer(); @@ -576,6 +580,8 @@ void FPGAViewWidget::mousePressEvent(QMouseEvent *event) lastDragPos_ = event->pos(); } if (event->buttons() & Qt::LeftButton) { + bool ctrl = QApplication::keyboardModifiers().testFlag(Qt::ControlModifier); + auto world = mouseToWorldCoordinates(event->x(), event->y()); auto closestOr = pickElement(world.x(), world.y()); if (!closestOr) @@ -583,9 +589,9 @@ void FPGAViewWidget::mousePressEvent(QMouseEvent *event) auto closest = closestOr.value(); if (closest.type == ElementType::BEL) { - clickedBel(closest.element.bel); + clickedBel(closest.element.bel, ctrl); } else if (closest.type == ElementType::WIRE) { - clickedWire(closest.element.wire); + clickedWire(closest.element.wire, ctrl); } } } diff --git a/gui/fpgaviewwidget.h b/gui/fpgaviewwidget.h index 732f4e3e..335ee51b 100644 --- a/gui/fpgaviewwidget.h +++ b/gui/fpgaviewwidget.h @@ -110,7 +110,7 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions public Q_SLOTS: void newContext(Context *ctx); - void onSelectedArchItem(std::vector decals); + void onSelectedArchItem(std::vector decals, bool keep); void onHighlightGroupChanged(std::vector decals, int group); void pokeRenderer(void); void zoomIn(); @@ -119,8 +119,8 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions void zoomOutbound(); Q_SIGNALS: - void clickedBel(BelId bel); - void clickedWire(WireId wire); + void clickedBel(BelId bel, bool add); + void clickedWire(WireId wire, bool add); private: const float zoomNear_ = 0.1f; // do not zoom closer than this -- cgit v1.2.3 From 3967899a2a81e91bd6b9c0a4ef7fd2ed9babd054 Mon Sep 17 00:00:00 2001 From: Sergiusz Bazanski Date: Fri, 27 Jul 2018 02:17:04 +0100 Subject: gui: don't keep stale hover selections --- gui/fpgaviewwidget.cc | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/gui/fpgaviewwidget.cc b/gui/fpgaviewwidget.cc index 5d7cb8ed..6dd911f4 100644 --- a/gui/fpgaviewwidget.cc +++ b/gui/fpgaviewwidget.cc @@ -612,8 +612,14 @@ void FPGAViewWidget::mouseMoveEvent(QMouseEvent *event) auto world = mouseToWorldCoordinates(event->x(), event->y()); auto closestOr = pickElement(world.x(), world.y()); - if (!closestOr) + // No elements? No decal. + if (!closestOr) { + QMutexLocker locked(&rendererArgsLock_); + rendererArgs_->hoveredDecal = DecalXY(); + rendererArgs_->changed = true; + pokeRenderer(); return; + } auto closest = closestOr.value(); -- cgit v1.2.3 From e1dfbc0241c4442398ef4e97e4567a1129a9d75e Mon Sep 17 00:00:00 2001 From: Sergiusz Bazanski Date: Fri, 27 Jul 2018 02:24:00 +0100 Subject: gui: do not allow selecting FRAMEs --- gui/fpgaviewwidget.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/fpgaviewwidget.cc b/gui/fpgaviewwidget.cc index 6dd911f4..d51250bf 100644 --- a/gui/fpgaviewwidget.cc +++ b/gui/fpgaviewwidget.cc @@ -231,7 +231,7 @@ void FPGAViewWidget::populateQuadTree(RendererData *data, const DecalXY &decal, float y = decal.y; for (auto &el : ctx_->getDecalGraphics(decal.decal)) { - if (el.style == GraphicElement::STYLE_HIDDEN) { + if (el.style == GraphicElement::STYLE_HIDDEN || el.style == GraphicElement::STYLE_FRAME) { continue; } -- cgit v1.2.3 From 96608c8d07dd149c033c98172e79d15e7bd2b69c Mon Sep 17 00:00:00 2001 From: Sergiusz Bazanski Date: Fri, 27 Jul 2018 02:28:01 +0100 Subject: gui: allow selecting pips --- gui/basewindow.cc | 2 ++ gui/designwidget.cc | 8 ++++++++ gui/designwidget.h | 1 + gui/fpgaviewwidget.cc | 2 ++ gui/fpgaviewwidget.h | 1 + 5 files changed, 14 insertions(+) diff --git a/gui/basewindow.cc b/gui/basewindow.cc index 96799e85..6e997011 100644 --- a/gui/basewindow.cc +++ b/gui/basewindow.cc @@ -2,6 +2,7 @@ * nextpnr -- Next Generation Place and Route * * Copyright (C) 2018 Miodrag Milanovic + * Copyright (C) 2018 Serge Bazanski * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -84,6 +85,7 @@ BaseMainWindow::BaseMainWindow(std::unique_ptr context, QWidget *parent SLOT(onSelectedArchItem(std::vector, bool))); connect(fpgaView, SIGNAL(clickedBel(BelId, bool)), designview, SLOT(onClickedBel(BelId, bool))); connect(fpgaView, SIGNAL(clickedWire(WireId, bool)), designview, SLOT(onClickedWire(WireId, bool))); + connect(fpgaView, SIGNAL(clickedPip(PipId, bool)), designview, SLOT(onClickedPip(PipId, bool))); connect(designview, SIGNAL(highlight(std::vector, int)), fpgaView, SLOT(onHighlightGroupChanged(std::vector, int))); diff --git a/gui/designwidget.cc b/gui/designwidget.cc index 674c74c7..d55c84e9 100644 --- a/gui/designwidget.cc +++ b/gui/designwidget.cc @@ -2,6 +2,7 @@ * nextpnr -- Next Generation Place and Route * * Copyright (C) 2018 Miodrag Milanovic + * Copyright (C) 2018 Serge Bazanski * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -521,6 +522,13 @@ void DesignWidget::onClickedWire(WireId wire, bool keep) Q_EMIT selected(getDecals(ElementType::WIRE, ctx->getWireName(wire)), keep); } +void DesignWidget::onClickedPip(PipId pip, bool keep) +{ + QTreeWidgetItem *item = nameToItem[getElementIndex(ElementType::PIP)].value(ctx->getPipName(pip).c_str(ctx)); + treeWidget->setCurrentItem(item); + Q_EMIT selected(getDecals(ElementType::PIP, ctx->getPipName(pip)), keep); +} + void DesignWidget::onItemSelectionChanged() { if (treeWidget->selectedItems().size() == 0) diff --git a/gui/designwidget.h b/gui/designwidget.h index a11ed062..60291cf3 100644 --- a/gui/designwidget.h +++ b/gui/designwidget.h @@ -77,6 +77,7 @@ class DesignWidget : public QWidget void updateTree(); void onClickedBel(BelId bel, bool keep); void onClickedWire(WireId wire, bool keep); + void onClickedPip(PipId pip, bool keep); private: Context *ctx; diff --git a/gui/fpgaviewwidget.cc b/gui/fpgaviewwidget.cc index d51250bf..31271965 100644 --- a/gui/fpgaviewwidget.cc +++ b/gui/fpgaviewwidget.cc @@ -592,6 +592,8 @@ void FPGAViewWidget::mousePressEvent(QMouseEvent *event) clickedBel(closest.element.bel, ctrl); } else if (closest.type == ElementType::WIRE) { clickedWire(closest.element.wire, ctrl); + } else if (closest.type == ElementType::PIP) { + clickedPip(closest.element.pip, ctrl); } } } diff --git a/gui/fpgaviewwidget.h b/gui/fpgaviewwidget.h index 335ee51b..690b12b8 100644 --- a/gui/fpgaviewwidget.h +++ b/gui/fpgaviewwidget.h @@ -121,6 +121,7 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions Q_SIGNALS: void clickedBel(BelId bel, bool add); void clickedWire(WireId wire, bool add); + void clickedPip(PipId pip, bool add); private: const float zoomNear_ = 0.1f; // do not zoom closer than this -- cgit v1.2.3 From dc46eea24d37311e28bc5830e899a77a625a075e Mon Sep 17 00:00:00 2001 From: Sergiusz Bazanski Date: Fri, 27 Jul 2018 13:46:44 +0100 Subject: gui: allow building for ECP5 and on Windows --- gui/fpgaviewwidget.h | 20 ++++++++++++++++++++ gui/quadtree.h | 10 +++++----- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/gui/fpgaviewwidget.h b/gui/fpgaviewwidget.h index 690b12b8..73021dbe 100644 --- a/gui/fpgaviewwidget.h +++ b/gui/fpgaviewwidget.h @@ -143,12 +143,32 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions Inner(WireId _wire) : wire(_wire) {} Inner(PipId _pip) : pip(_pip) {} Inner(GroupId _group) : group(_group) {} + Inner() {} } 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) {} + PickedElement(const PickedElement &other) : type(other.type) + { + switch (type) { + case ElementType::BEL: + element.bel = other.element.bel; + break; + case ElementType::WIRE: + element.wire = other.element.wire; + break; + case ElementType::PIP: + element.pip = other.element.pip; + break; + case ElementType::GROUP: + element.group = other.element.group; + break; + default: + NPNR_ASSERT_FALSE("Invalid ElementType"); + } + } DecalXY decal(Context *ctx) const { diff --git a/gui/quadtree.h b/gui/quadtree.h index 22065c2f..b3ebf81c 100644 --- a/gui/quadtree.h +++ b/gui/quadtree.h @@ -157,7 +157,7 @@ template class QuadTreeNode // - any of the 4 children nodes. enum Quadrant { - THIS = -1, + THIS_NODE = -1, NW = 0, NE = 1, SW = 2, @@ -171,7 +171,7 @@ template class QuadTreeNode Quadrant quadrant(const BoundingBox &b) const { if (children_ == nullptr) { - return THIS; + return THIS_NODE; } bool west0 = b.x0_ < splitx_; @@ -187,7 +187,7 @@ template class QuadTreeNode return SW; if (!west0 && !west1 && !north0 && !north1) return SE; - return THIS; + return THIS_NODE; } // Checks whether this node should split. @@ -252,7 +252,7 @@ template class QuadTreeNode // Put the element either recursively into a child if it fits // entirely or keep it for ourselves if not. auto quad = quadrant(k); - if (quad == THIS) { + if (quad == THIS_NODE) { elems_.push_back(BoundElement(k, std::move(v))); } else { return children_[quad].insert(k, std::move(v)); @@ -286,7 +286,7 @@ template class QuadTreeNode auto it = elems_.begin(); while (it != elems_.end()) { auto quad = quadrant(it->bb_); - if (quad != THIS) { + if (quad != THIS_NODE) { // Move to one of the children. if (!children_[quad].insert(it->bb_, std::move(it->elem_))) return false; -- cgit v1.2.3 From 816d33fa94bc14f05a90b259cfef9d693734e0df Mon Sep 17 00:00:00 2001 From: Sergiusz Bazanski Date: Fri, 27 Jul 2018 15:11:41 +0100 Subject: gui: deunionize PickedElement to support arches with typedefd Ids --- gui/fpgaviewwidget.cc | 27 ++++++++++++------ gui/fpgaviewwidget.h | 79 +++++++++++++++++++++++++++++++++------------------ 2 files changed, 69 insertions(+), 37 deletions(-) diff --git a/gui/fpgaviewwidget.cc b/gui/fpgaviewwidget.cc index 31271965..8a27c7c6 100644 --- a/gui/fpgaviewwidget.cc +++ b/gui/fpgaviewwidget.cc @@ -170,7 +170,8 @@ float FPGAViewWidget::PickedElement::distance(Context *ctx, float wx, float wy) }); } -void FPGAViewWidget::renderGraphicElement(LineShaderData &out, PickQuadTree::BoundingBox &bb, const GraphicElement &el, float x, float y) +void FPGAViewWidget::renderGraphicElement(LineShaderData &out, PickQuadTree::BoundingBox &bb, const GraphicElement &el, + float x, float y) { if (el.type == GraphicElement::TYPE_BOX) { auto line = PolyLine(true); @@ -199,6 +200,9 @@ void FPGAViewWidget::renderGraphicElement(LineShaderData &out, PickQuadTree::Bou void FPGAViewWidget::renderDecal(LineShaderData &out, PickQuadTree::BoundingBox &bb, const DecalXY &decal) { + if (decal.decal == DecalId()) + return; + float offsetX = decal.x; float offsetY = decal.y; @@ -207,7 +211,8 @@ void FPGAViewWidget::renderDecal(LineShaderData &out, PickQuadTree::BoundingBox } } -void FPGAViewWidget::renderArchDecal(LineShaderData out[GraphicElement::STYLE_MAX], PickQuadTree::BoundingBox &bb, const DecalXY &decal) +void FPGAViewWidget::renderArchDecal(LineShaderData out[GraphicElement::STYLE_MAX], PickQuadTree::BoundingBox &bb, + const DecalXY &decal) { float offsetX = decal.x; float offsetY = decal.y; @@ -444,16 +449,20 @@ void FPGAViewWidget::renderLines(void) // Populate picking quadtree. data->qt = std::unique_ptr(new PickQuadTree(data->bbGlobal)); for (auto const &decal : belDecals) { - populateQuadTree(data.get(), decal.first, PickedElement(decal.second, decal.first.x, decal.first.y)); + populateQuadTree(data.get(), decal.first, + PickedElement::fromBel(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)); + populateQuadTree(data.get(), decal.first, + PickedElement::fromWire(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)); + populateQuadTree(data.get(), decal.first, + PickedElement::fromPip(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)); + populateQuadTree(data.get(), decal.first, + PickedElement::fromGroup(decal.second, decal.first.x, decal.first.y)); } // Swap over. @@ -589,11 +598,11 @@ void FPGAViewWidget::mousePressEvent(QMouseEvent *event) auto closest = closestOr.value(); if (closest.type == ElementType::BEL) { - clickedBel(closest.element.bel, ctrl); + clickedBel(closest.bel, ctrl); } else if (closest.type == ElementType::WIRE) { - clickedWire(closest.element.wire, ctrl); + clickedWire(closest.wire, ctrl); } else if (closest.type == ElementType::PIP) { - clickedPip(closest.element.pip, ctrl); + clickedPip(closest.pip, ctrl); } } } diff --git a/gui/fpgaviewwidget.h b/gui/fpgaviewwidget.h index 73021dbe..6b94a126 100644 --- a/gui/fpgaviewwidget.h +++ b/gui/fpgaviewwidget.h @@ -124,7 +124,7 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions void clickedPip(PipId pip, bool add); private: - const float zoomNear_ = 0.1f; // do not zoom closer than this + const float zoomNear_ = 0.1f; // do not zoom closer than this const float zoomFar_ = 30.0f; // do not zoom further than this const float zoomLvl1_ = 1.0f; const float zoomLvl2_ = 5.0f; @@ -132,38 +132,59 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions 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) {} - Inner() {} - } element; + + // These are not in an union (and thus this structure is very verbose + // and somewhat heavy) because the Id types are typedef'd to StringIds + // in the generic architecture. Once that changes (or we get an AnyId + // construct from Arches), this should go away. + BelId bel; + WireId wire; + PipId pip; + GroupId group; + 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) {} + + PickedElement(ElementType type, float x, float y) : type(type), x(x), y(y) {} + + static PickedElement fromBel(BelId bel, float x, float y) + { + PickedElement e(ElementType::BEL, x, y); + e.bel = bel; + return e; + } + static PickedElement fromWire(WireId wire, float x, float y) + { + PickedElement e(ElementType::WIRE, x, y); + e.wire = wire; + return e; + } + static PickedElement fromPip(PipId pip, float x, float y) + { + PickedElement e(ElementType::PIP, x, y); + e.pip = pip; + return e; + } + static PickedElement fromGroup(GroupId group, float x, float y) + { + PickedElement e(ElementType::GROUP, x, y); + e.group = group; + return e; + } + PickedElement(const PickedElement &other) : type(other.type) { switch (type) { case ElementType::BEL: - element.bel = other.element.bel; + bel = other.bel; break; case ElementType::WIRE: - element.wire = other.element.wire; + wire = other.wire; break; case ElementType::PIP: - element.pip = other.element.pip; + pip = other.pip; break; case ElementType::GROUP: - element.group = other.element.group; + group = other.group; break; default: NPNR_ASSERT_FALSE("Invalid ElementType"); @@ -175,16 +196,16 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions DecalXY decal; switch (type) { case ElementType::BEL: - decal = ctx->getBelDecal(element.bel); + decal = ctx->getBelDecal(bel); break; case ElementType::WIRE: - decal = ctx->getWireDecal(element.wire); + decal = ctx->getWireDecal(wire); break; case ElementType::PIP: - decal = ctx->getPipDecal(element.pip); + decal = ctx->getPipDecal(pip); break; case ElementType::GROUP: - decal = ctx->getGroupDecal(element.group); + decal = ctx->getGroupDecal(group); break; default: NPNR_ASSERT_FALSE("Invalid ElementType"); @@ -272,9 +293,11 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions void zoomToBB(const PickQuadTree::BoundingBox &bb); void zoom(int level); void renderLines(void); - void renderGraphicElement(LineShaderData &out, PickQuadTree::BoundingBox &bb, const GraphicElement &el, float x, float y); + void renderGraphicElement(LineShaderData &out, PickQuadTree::BoundingBox &bb, const GraphicElement &el, float x, + float y); void renderDecal(LineShaderData &out, PickQuadTree::BoundingBox &bb, const DecalXY &decal); - void renderArchDecal(LineShaderData out[GraphicElement::STYLE_MAX], PickQuadTree::BoundingBox &bb, const DecalXY &decal); + void renderArchDecal(LineShaderData out[GraphicElement::STYLE_MAX], PickQuadTree::BoundingBox &bb, + const DecalXY &decal); void populateQuadTree(RendererData *data, const DecalXY &decal, const PickedElement &element); boost::optional pickElement(float worldx, float worldy); QVector4D mouseToWorldCoordinates(int x, int y); -- cgit v1.2.3 From c210ce77688677a933e9b2906452766f0c344f33 Mon Sep 17 00:00:00 2001 From: Sergiusz Bazanski Date: Fri, 27 Jul 2018 15:46:00 +0100 Subject: gui: unselect on click and zoom fixes --- gui/fpgaviewwidget.cc | 30 ++++++++++++++++++++++++------ gui/fpgaviewwidget.h | 3 ++- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/gui/fpgaviewwidget.cc b/gui/fpgaviewwidget.cc index 8a27c7c6..ed25a187 100644 --- a/gui/fpgaviewwidget.cc +++ b/gui/fpgaviewwidget.cc @@ -593,8 +593,17 @@ void FPGAViewWidget::mousePressEvent(QMouseEvent *event) auto world = mouseToWorldCoordinates(event->x(), event->y()); auto closestOr = pickElement(world.x(), world.y()); - if (!closestOr) + if (!closestOr) { + // If we clicked on empty space and aren't holding down ctrl, + // clear the selection. + if (!ctrl) { + QMutexLocker locked(&rendererArgsLock_); + rendererArgs_->selectedDecals.clear(); + rendererArgs_->changed = true; + pokeRenderer(); + } return; + } auto closest = closestOr.value(); if (closest.type == ElementType::BEL) { @@ -709,11 +718,19 @@ void FPGAViewWidget::zoom(int level) update(); } +void FPGAViewWidget::clampZoom() +{ + if (zoom_ < zoomNear_) + zoom_ = zoomNear_; + else if (zoom_ > zoomFar_) + zoom_ = zoomFar_; +} + void FPGAViewWidget::zoomIn() { zoom(10); } void FPGAViewWidget::zoomOut() { zoom(-10); } -void FPGAViewWidget::zoomToBB(const PickQuadTree::BoundingBox &bb) +void FPGAViewWidget::zoomToBB(const PickQuadTree::BoundingBox &bb, float margin) { if (bb.w() < 0.00005 && bb.h() < 0.00005) return; @@ -723,16 +740,17 @@ void FPGAViewWidget::zoomToBB(const PickQuadTree::BoundingBox &bb) // Our FOV is π/2, so distance for camera to see a plane of width H is H/2. // We add 1 unit to cover half a unit of extra space around. - float distance_w = bb.w() / 2 + 1; - float distance_h = bb.h() / 2 + 1; + float distance_w = bb.w() / 2 + margin; + float distance_h = bb.h() / 2 + margin; zoom_ = std::max(distance_w, distance_h); + clampZoom(); } void FPGAViewWidget::zoomSelected() { { QMutexLocker lock(&rendererDataLock_); - zoomToBB(rendererData_->bbSelected); + zoomToBB(rendererData_->bbSelected, 0.5f); } update(); } @@ -741,7 +759,7 @@ void FPGAViewWidget::zoomOutbound() { { QMutexLocker lock(&rendererDataLock_); - zoomToBB(rendererData_->bbGlobal); + zoomToBB(rendererData_->bbGlobal, 1.0f); } } diff --git a/gui/fpgaviewwidget.h b/gui/fpgaviewwidget.h index 6b94a126..c35821d9 100644 --- a/gui/fpgaviewwidget.h +++ b/gui/fpgaviewwidget.h @@ -290,7 +290,8 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions std::unique_ptr rendererData_; QMutex rendererDataLock_; - void zoomToBB(const PickQuadTree::BoundingBox &bb); + void clampZoom(); + void zoomToBB(const PickQuadTree::BoundingBox &bb, float margin); void zoom(int level); void renderLines(void); void renderGraphicElement(LineShaderData &out, PickQuadTree::BoundingBox &bb, const GraphicElement &el, float x, -- cgit v1.2.3