Commit 78ea73a0 authored by Yann Boucher's avatar Yann Boucher
Browse files

Significant performance improvements with GridView

parent bef2a147
Pipeline #78788 passed with stages
in 17 seconds
...@@ -295,7 +295,7 @@ pattern recorded :</string> ...@@ -295,7 +295,7 @@ pattern recorded :</string>
<number>1</number> <number>1</number>
</property> </property>
<property name="maximum"> <property name="maximum">
<number>9999</number> <number>1000</number>
</property> </property>
<property name="value"> <property name="value">
<number>10</number> <number>10</number>
...@@ -318,7 +318,7 @@ pattern recorded :</string> ...@@ -318,7 +318,7 @@ pattern recorded :</string>
<number>1</number> <number>1</number>
</property> </property>
<property name="maximum"> <property name="maximum">
<number>9999</number> <number>1000</number>
</property> </property>
<property name="value"> <property name="value">
<number>10</number> <number>10</number>
......
...@@ -12,6 +12,7 @@ Cette classe représente le widget utilisé pour l'affichage et l'interaction av ...@@ -12,6 +12,7 @@ Cette classe représente le widget utilisé pour l'affichage et l'interaction av
#define GRIDVIEW_HPP #define GRIDVIEW_HPP
#include <map> #include <map>
#include <set>
#include <QFrame> #include <QFrame>
#include <QGraphicsView> #include <QGraphicsView>
...@@ -48,9 +49,10 @@ signals: ...@@ -48,9 +49,10 @@ signals:
void zoomed(); void zoomed();
}; };
} }
class GridItem;
class GridView; class GridView;
class GridGraphicsView; class GridGraphicsView;
class DragDropHandlerItem;
/** /**
* \class GridView * \class GridView
...@@ -93,7 +95,7 @@ public: ...@@ -93,7 +95,7 @@ public:
//! \brief Retourne la taille du côté en pixels d'une cellule au niveau de zoom actuel. //! \brief Retourne la taille du côté en pixels d'une cellule au niveau de zoom actuel.
//! \returns Taille en pixel du côté d'une cellule. //! \returns Taille en pixel du côté d'une cellule.
unsigned cell_pixel_size() const; unsigned cell_screen_size() const;
//! \brief Retourne la taille de la grille, dans un QSize. //! \brief Retourne la taille de la grille, dans un QSize.
//! \returns La taille de la Grid. //! \returns La taille de la Grid.
...@@ -127,16 +129,22 @@ signals: ...@@ -127,16 +129,22 @@ signals:
void zoom_changed(unsigned cell_size); void zoom_changed(unsigned cell_size);
private: private:
GridItem* item_at(Coord pos);
const GridItem* item_at(Coord pos) const;
void load_grid(const Grid& grid); void load_grid(const Grid& grid);
void set_cell_state(Coord pos, unsigned state);
void update_gridview();
void copy_selection(); void copy_selection();
void paste_clipboard(); void paste_clipboard();
void delete_selection(); void delete_selection();
Coord top_left_of_selection() const; Coord top_left_of_selection() const;
const QPixmap &grid_pixmap() const;
private slots:
void handle_rubberband(QRect, QPointF, QPointF);
protected: protected:
void keyPressEvent(QKeyEvent *event); void keyPressEvent(QKeyEvent *event);
void keyReleaseEvent(QKeyEvent *event); void keyReleaseEvent(QKeyEvent *event);
...@@ -145,8 +153,15 @@ private: ...@@ -145,8 +153,15 @@ private:
GridGraphicsView* m_view; GridGraphicsView* m_view;
QGraphicsScene* m_scene; QGraphicsScene* m_scene;
detail::Graphics_view_zoom* m_zoom; detail::Graphics_view_zoom* m_zoom;
std::vector<GridItem*> m_item_cache; // cache pour pouvoir accéder efficacement à un Item selon une coordonnée
History m_undo_history; History m_undo_history;
QImage m_grid_image;
QPixmap m_grid_pixmap;
Grid m_grid;
std::set<Coord> m_selected_cells;
std::vector<QGraphicsRectItem*> m_selection_rects;
bool m_handling_rubberband;
DragDropHandlerItem* m_drag_drop_handler;
unsigned m_width; unsigned m_width;
unsigned m_height; unsigned m_height;
unsigned m_pen; unsigned m_pen;
......
...@@ -85,71 +85,20 @@ bool Graphics_view_zoom::eventFilter(QObject *object, QEvent *event) { ...@@ -85,71 +85,20 @@ bool Graphics_view_zoom::eventFilter(QObject *object, QEvent *event) {
} }
class GridItem : public QGraphicsRectItem class DragDropHandlerItem : public QGraphicsRectItem
{ {
public: public:
GridItem(GridView& in_gridview, unsigned state = 0, Coord pos = Coord{0, 0}, QGraphicsItem *parent = nullptr) DragDropHandlerItem(GridView& in_gridview, QGraphicsItem *parent = nullptr)
: QGraphicsRectItem(0, 0, GridItem::width(), GridItem::height(), parent), m_grid_view(in_gridview) : QGraphicsRectItem(0, 0, 1, 1, parent), m_grid_view(in_gridview)
{ {
setAcceptHoverEvents(true); setAcceptHoverEvents(true);
setToolTip(m_state_name);
setFlag(QGraphicsItem::ItemIsSelectable);
setAcceptDrops(true); setAcceptDrops(true);
//setCacheMode(QGraphicsItem::DeviceCoordinateCache);
set_state(state); setPen(Qt::NoPen);
set_grid_pos(pos); setBrush(Qt::NoBrush);
setPen(QPen(brush(), 0));
}
static int width()
{ return 20; }
static int height()
{ return 20; }
unsigned state() const
{ return m_cell_state; }
void set_state(unsigned s, bool update_history = false)
{
if (update_history && m_cell_state != s)
m_grid_view.push_history();
const Alphabet& alph = m_grid_view.alphabet();
setBrush(QBrush(stateColor_to_QColor(alph.getState(s).getColor())));
setToolTip(QString::fromStdString(alph.getState(s).getStateLabel()));
m_cell_state = s;
update();
}
Coord grid_pos() const
{ return m_cell_pos; }
void set_grid_pos(Coord c)
{
setPos(QPointF(c.x*GridItem::width(), c.y*GridItem::height()));
m_cell_pos = c;
} }
protected: protected:
void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override
{
QRectF rect = QRectF(0, 0, GridItem::width(), GridItem::height());
painter->setPen(Qt::NoPen);
if (isSelected())
{
QBrush selection_brush = brush();
selection_brush.setColor(brush().color().darker(175));
painter->setBrush(selection_brush);
}
else
{
painter->setBrush(brush());
}
painter->drawRect(rect);
}
void dragEnterEvent(QGraphicsSceneDragDropEvent *event) override void dragEnterEvent(QGraphicsSceneDragDropEvent *event) override
{ {
if (event->mimeData()->hasText()) if (event->mimeData()->hasText())
...@@ -175,12 +124,11 @@ protected: ...@@ -175,12 +124,11 @@ protected:
return; return;
} }
m_grid_view.paste_structure_at(m_cell_pos, s); Coord coord = Coord{(int)event->pos().x(), (int)event->pos().y()};
m_grid_view.paste_structure_at(coord, s);
} }
private: private:
Coord m_cell_pos;
unsigned m_cell_state;
QString m_state_name; QString m_state_name;
GridView& m_grid_view; GridView& m_grid_view;
}; };
...@@ -204,6 +152,7 @@ protected: ...@@ -204,6 +152,7 @@ protected:
void mousePressEvent(QMouseEvent* event); void mousePressEvent(QMouseEvent* event);
void mouseMoveEvent(QMouseEvent* event); void mouseMoveEvent(QMouseEvent* event);
void drawForeground(QPainter * painter, const QRectF & rect); void drawForeground(QPainter * painter, const QRectF & rect);
void drawBackground(QPainter * painter, const QRectF & rect);
private: private:
void draw_bresenham(QPointF from, QPointF to); void draw_bresenham(QPointF from, QPointF to);
...@@ -217,42 +166,47 @@ private: ...@@ -217,42 +166,47 @@ private:
void GridGraphicsView::mousePressEvent(QMouseEvent *event) void GridGraphicsView::mousePressEvent(QMouseEvent *event)
{ {
QPointF item_pos = mapToScene(event->pos()); QPointF item_pos = mapToScene(event->pos());
Coord coord = Coord{(int)item_pos.x()/GridItem::width(), (int)item_pos.y()/GridItem::height()}; if (!sceneRect().contains(item_pos))
GridItem* item = m_gridview.item_at(coord); return;
Coord coord = Coord{(int)item_pos.x(), (int)item_pos.y()};
m_last_mouse_pos = event->pos(); m_last_mouse_pos = event->pos();
if (QGuiApplication::keyboardModifiers() == Qt::NoModifier && event->button() == Qt::LeftButton) if (QGuiApplication::keyboardModifiers() == Qt::NoModifier && event->button() == Qt::LeftButton)
{ {
if (item) unsigned state = m_gridview.current_pen();
{ m_gridview.set_cell_state(coord, state);
unsigned state = m_gridview.current_pen(); m_gridview.update_gridview();
item->set_state(state, true);
}
} }
else if (event->button() == Qt::RightButton && item) else if (event->button() == Qt::RightButton)
{ {
if ((QGuiApplication::keyboardModifiers() & Qt::ControlModifier) == 0)
m_gridview.clear_selection();
QGraphicsView::mousePressEvent(event); QGraphicsView::mousePressEvent(event);
item->setSelected(true); //item->setSelected(true);
} }
else else
QGraphicsView::mousePressEvent(event); QGraphicsView::mousePressEvent(event);
} }
void GridGraphicsView::mouseMoveEvent(QMouseEvent *event) void GridGraphicsView::mouseMoveEvent(QMouseEvent *event)
{ {
QPointF item_pos = mapToScene(event->pos()); QPointF item_pos = mapToScene(event->pos());
Coord coord = Coord{(int)item_pos.x()/GridItem::width(), (int)item_pos.y()/GridItem::height()}; if (!sceneRect().contains(item_pos) || !sceneRect().contains(mapToScene(m_last_mouse_pos)))
GridItem* item = m_gridview.item_at(coord); return;
if (QGuiApplication::keyboardModifiers() == Qt::NoModifier && event->buttons() == Qt::LeftButton) if (QGuiApplication::keyboardModifiers() == Qt::NoModifier && event->buttons() == Qt::LeftButton)
{ {
draw_bresenham(mapToScene(m_last_mouse_pos), mapToScene(event->pos())); draw_bresenham(mapToScene(m_last_mouse_pos), item_pos);
m_gridview.update_gridview();
} }
else if (event->buttons() == Qt::RightButton && item) else if (event->buttons() == Qt::RightButton)
{ {
QGraphicsView::mouseMoveEvent(event); QGraphicsView::mouseMoveEvent(event);
item->setSelected(true); //item->setSelected(true);
} }
else else
QGraphicsView::mouseMoveEvent(event); QGraphicsView::mouseMoveEvent(event);
...@@ -260,11 +214,11 @@ void GridGraphicsView::mouseMoveEvent(QMouseEvent *event) ...@@ -260,11 +214,11 @@ void GridGraphicsView::mouseMoveEvent(QMouseEvent *event)
m_last_mouse_pos = event->pos(); m_last_mouse_pos = event->pos();
} }
void GridGraphicsView::drawForeground(QPainter *painter, const QRectF &rect) void GridGraphicsView::drawForeground(QPainter *painter, const QRectF &)
{ {
qreal lod = QStyleOptionGraphicsItem::levelOfDetailFromTransform(painter->worldTransform()); qreal lod = QStyleOptionGraphicsItem::levelOfDetailFromTransform(painter->worldTransform());
if (lod < 0.4) if (lod < 10)
return; // trop zoomé, on ne dessine rien return; // trop zoomé, on ne dessine rien
painter->setPen(QPen(Qt::black, 0)); // cosmetic 1-pixel pen painter->setPen(QPen(Qt::black, 0)); // cosmetic 1-pixel pen
...@@ -273,14 +227,14 @@ void GridGraphicsView::drawForeground(QPainter *painter, const QRectF &rect) ...@@ -273,14 +227,14 @@ void GridGraphicsView::drawForeground(QPainter *painter, const QRectF &rect)
// horizontal lines // horizontal lines
for (int i = -1; i < m_gridview.grid_size().height(); ++i) for (int i = -1; i < m_gridview.grid_size().height(); ++i)
{ {
const unsigned y_offset = (i+1)*GridItem::height(); const unsigned y_offset = (i+1);
lines.append(QLine(QPoint(0, y_offset), QPoint(GridItem::width()*m_gridview.grid_size().width(), y_offset))); lines.append(QLine(QPoint(0, y_offset), QPoint(m_gridview.grid_size().width(), y_offset)));
} }
// vertical lines // vertical lines
for (int j = -1; j < m_gridview.grid_size().width(); ++j) for (int j = -1; j < m_gridview.grid_size().width(); ++j)
{ {
const unsigned x_offset = (j+1)*GridItem::width(); const unsigned x_offset = (j+1);
lines.append(QLine(QPoint(x_offset, 0), QPoint(x_offset, GridItem::height()*m_gridview.grid_size().height()))); lines.append(QLine(QPoint(x_offset, 0), QPoint(x_offset, m_gridview.grid_size().height())));
} }
painter->drawLines(lines); painter->drawLines(lines);
...@@ -288,13 +242,20 @@ void GridGraphicsView::drawForeground(QPainter *painter, const QRectF &rect) ...@@ -288,13 +242,20 @@ void GridGraphicsView::drawForeground(QPainter *painter, const QRectF &rect)
painter->setPen(Qt::NoPen); painter->setPen(Qt::NoPen);
} }
void GridGraphicsView::drawBackground(QPainter *painter, const QRectF &rect)
{
QGraphicsView::drawBackground(painter, rect);
painter->drawPixmap(0, 0, m_gridview.grid_pixmap());
}
// Algorithme de Bresenham pour tracer une ligne entre les deux points et dessiner sur ces items // Algorithme de Bresenham pour tracer une ligne entre les deux points et dessiner sur ces items
void GridGraphicsView::draw_bresenham(QPointF from, QPointF to) void GridGraphicsView::draw_bresenham(QPointF from, QPointF to)
{ {
int x0 = from.x()/GridItem::width(); int x0 = from.x();
int y0 = from.y()/GridItem::height(); int y0 = from.y();
int x1 = to.x()/GridItem::width(); int x1 = to.x();
int y1 = to.y()/GridItem::height(); int y1 = to.y();
// cf. https://gist.github.com/bert/1085538 // cf. https://gist.github.com/bert/1085538
int dx = abs (x1 - x0), sx = x0 < x1 ? 1 : -1; int dx = abs (x1 - x0), sx = x0 < x1 ? 1 : -1;
...@@ -303,10 +264,8 @@ void GridGraphicsView::draw_bresenham(QPointF from, QPointF to) ...@@ -303,10 +264,8 @@ void GridGraphicsView::draw_bresenham(QPointF from, QPointF to)
for (;;){ /* loop */ for (;;){ /* loop */
//printf("Trying %d %d\n", x, y); m_gridview.set_cell_state(Coord{x0, y0}, m_gridview.current_pen());
auto item = m_gridview.item_at(Coord{x0, y0});
if (item)
item->set_state(m_gridview.current_pen());
if (x0 == x1 && y0 == y1) break; if (x0 == x1 && y0 == y1) break;
e2 = 2 * err; e2 = 2 * err;
if (e2 >= dy) { err += dy; x0 += sx; } /* e_xy+e_x > 0 */ if (e2 >= dy) { err += dy; x0 += sx; } /* e_xy+e_x > 0 */
...@@ -315,11 +274,12 @@ void GridGraphicsView::draw_bresenham(QPointF from, QPointF to) ...@@ -315,11 +274,12 @@ void GridGraphicsView::draw_bresenham(QPointF from, QPointF to)
} }
GridView::GridView(QWidget *parent) GridView::GridView(QWidget *parent)
: QFrame(parent), m_undo_history(10), m_width(10), m_height(10) : QFrame(parent), m_undo_history(10), m_grid(10, 10), m_handling_rubberband(false), m_width(10), m_height(10)
{ {
//setMouseTracking(true); //setMouseTracking(true);
setFrameStyle(Sunken | StyledPanel); setFrameStyle(Sunken | StyledPanel);
setAcceptDrops(true);
m_scene = new QGraphicsScene(this); m_scene = new QGraphicsScene(this);
...@@ -333,13 +293,6 @@ GridView::GridView(QWidget *parent) ...@@ -333,13 +293,6 @@ GridView::GridView(QWidget *parent)
m_view->viewport()->setCursor(Qt::ArrowCursor); m_view->viewport()->setCursor(Qt::ArrowCursor);
m_view->setBackgroundBrush(QBrush(Qt::gray)); m_view->setBackgroundBrush(QBrush(Qt::gray));
m_view->setRubberBandSelectionMode(Qt::IntersectsItemBoundingRect); // provides better performance m_view->setRubberBandSelectionMode(Qt::IntersectsItemBoundingRect); // provides better performance
/*
QOpenGLWidget *gl = new QOpenGLWidget();
QSurfaceFormat format;
format.setSamples(4);
gl->setFormat(format);
m_view->setViewport(gl);
*/
m_zoom = new detail::Graphics_view_zoom(m_view); m_zoom = new detail::Graphics_view_zoom(m_view);
...@@ -355,6 +308,9 @@ GridView::GridView(QWidget *parent) ...@@ -355,6 +308,9 @@ GridView::GridView(QWidget *parent)
alph.newEtat(state{stateColor{0, 0, 255}, "Alive"}); alph.newEtat(state{stateColor{0, 0, 255}, "Alive"});
set_alphabet(alph); set_alphabet(alph);
m_drag_drop_handler = new DragDropHandlerItem(*this);
m_scene->addItem(m_drag_drop_handler);
// Initialisation la première fois // Initialisation la première fois
m_height = 10; m_height = 10;
m_width = 10; m_width = 10;
...@@ -364,8 +320,9 @@ GridView::GridView(QWidget *parent) ...@@ -364,8 +320,9 @@ GridView::GridView(QWidget *parent)
connect(m_zoom, &detail::Graphics_view_zoom::zoomed, this, [this] connect(m_zoom, &detail::Graphics_view_zoom::zoomed, this, [this]
{ {
emit zoom_changed(cell_pixel_size()); emit zoom_changed(cell_screen_size());
}); });
connect(m_view, &QGraphicsView::rubberBandChanged, this, &GridView::handle_rubberband);
} }
void GridView::set_alphabet(const Alphabet &alph) void GridView::set_alphabet(const Alphabet &alph)
...@@ -396,9 +353,9 @@ void GridView::set_clipboard(const Structure &s) ...@@ -396,9 +353,9 @@ void GridView::set_clipboard(const Structure &s)
m_copy_paste_buffer = s; m_copy_paste_buffer = s;
} }
unsigned GridView::cell_pixel_size() const unsigned GridView::cell_screen_size() const
{ {
return m_view->transform().mapRect(QRectF(0,0,GridItem::width(), GridItem::height())).size().toSize().width(); return m_view->transform().mapRect(QRectF(0,0,1,1)).size().toSize().width();
} }
QSize GridView::grid_size() const QSize GridView::grid_size() const
...@@ -410,30 +367,23 @@ void GridView::copy_grid(const Grid &grid) ...@@ -410,30 +367,23 @@ void GridView::copy_grid(const Grid &grid)
{ {
push_history(); push_history();
load_grid(grid); load_grid(grid);
update_gridview();
} }
Grid GridView::get_grid() const Grid GridView::get_grid() const
{ {
Grid grid = Grid(m_height, m_width); return m_grid;
for (size_t i = 0; i < grid.get_col(); ++i)
{
for (size_t j = 0; j < grid.get_rows(); ++j)
{
const GridItem* item = item_at(Coord{(int)i, (int)j});
assert(item);
Coord pos;
pos.x = i;
pos.y = j;
grid.set_cell(pos, item->state());
}
}
return grid;
} }
void GridView::clear_selection() void GridView::clear_selection()
{ {
m_scene->clearSelection(); for (auto rect : m_selection_rects)
{
m_scene->removeItem(rect);
}
m_selection_rects.clear();
m_selected_cells.clear();
m_handling_rubberband = false;
} }
Structure GridView::selected_cells() const Structure GridView::selected_cells() const
...@@ -443,12 +393,9 @@ Structure GridView::selected_cells() const ...@@ -443,12 +393,9 @@ Structure GridView::selected_cells() const
std::vector<std::pair<Coord, unsigned>> cells; std::vector<std::pair<Coord, unsigned>> cells;
// on calcule la position du coin supérieur gauche de la sélection, afin de la considérer comme l'origine de de la structure for (auto coord : m_selected_cells)
Coord origin = top_left_of_selection();
Q_FOREACH(const auto& item, m_scene->selectedItems())
{ {
GridItem* cell = static_cast<GridItem*>(item); cells.push_back(std::make_pair(coord, m_grid.get_state(coord)));
cells.push_back(std::make_pair(cell->grid_pos() - origin, cell->state()));
} }
return Structure(cells.begin(), cells.end()); return Structure(cells.begin(), cells.end());
} }
...@@ -466,65 +413,37 @@ void GridView::undo() ...@@ -466,65 +413,37 @@ void GridView::undo()
} }
} }
GridItem *GridView::item_at(Coord pos)
{
if (pos.x < 0 || pos.y < 0 || pos.x >= (int)m_width || pos.y >= (int)m_height)
return nullptr;
return m_item_cache[pos.y*m_width + pos.x];
//QGraphicsItem* gitem_ptr = m_scene->itemAt(QPointF(pos.x*GridItem::width(), pos.y*GridItem::height()), QTransform());
//return dynamic_cast<GridItem*>(gitem_ptr);
}
const GridItem *GridView::item_at(Coord pos) const
{
if (pos.x < 0 || pos.y < 0 || pos.x >= (int)m_width || pos.y >= (int)m_height)
return nullptr;
return m_item_cache[pos.y*m_width + pos.x];
//QGraphicsItem* gitem_ptr = m_scene->itemAt(QPointF(pos.x*GridItem::width(), pos.y*GridItem::height()), QTransform());
//return dynamic_cast<GridItem*>(gitem_ptr);
}
void GridView::load_grid(const Grid &grid) void GridView::load_grid(const Grid &grid)
{ {
m_height = grid.get_rows(); m_height = grid.get_rows();
m_width = grid.get_col(); m_width = grid.get_col();
if (m_height*m_width != m_item_cache.size())