New ListView widget, based off of QGraphicsView, for fast and memory efficient list views

This commit is contained in:
Nav
2023-03-05 23:26:14 +00:00
parent 3ce1d7df43
commit 9cceb8b93c
7 changed files with 301 additions and 0 deletions

View File

@@ -0,0 +1,31 @@
#pragma once
#include <QGraphicsItem>
#include <QSize>
#include <QRectF>
#include <QPainter>
#include <QWidget>
#include <QGraphicsView>
#include <QGraphicsScene>
namespace Bloom::Widgets
{
class ListItem: public QGraphicsItem
{
public:
bool selected = false;
QSize size = QSize();
ListItem() = default;
[[nodiscard]] QRectF boundingRect() const override {
return QRectF(QPointF(0, 0), this->size);
}
virtual void onGeometryChanged() {
return;
}
void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override = 0;
};
}

View File

@@ -0,0 +1,129 @@
#include "ListScene.hpp"
#include <QMenu>
#include <QApplication>
#include <QClipboard>
#include <QByteArray>
#include <algorithm>
namespace Bloom::Widgets
{
ListScene::ListScene(
std::vector<ListItem*> items,
QGraphicsView* parent
)
: listItems(std::move(items))
, parent(parent)
, QGraphicsScene(parent)
{
this->setItemIndexMethod(QGraphicsScene::NoIndex);
for (const auto& listItem : this->listItems) {
this->addItem(listItem);
}
}
void ListScene::refreshGeometry() {
const auto* viewport = this->viewport();
const auto viewportWidth = viewport != nullptr ? viewport->width() : 0;
const auto viewportHeight = viewport != nullptr ? viewport->height() : 0;
const auto startXPosition = this->margins.left();
auto startYPosition = this->margins.top();
for (auto& listItem : this->listItems) {
if (!listItem->isVisible()) {
continue;
}
listItem->size.setWidth(viewportWidth);
listItem->setPos(startXPosition, startYPosition);
listItem->onGeometryChanged();
startYPosition += listItem->size.height();
}
this->setSceneRect(0, 0, viewportWidth, std::max(viewportHeight, startYPosition));
this->update();
}
void ListScene::setEnabled(bool enabled) {
if (this->enabled == enabled) {
return;
}
this->enabled = enabled;
for (auto& item : this->listItems) {
item->setEnabled(this->enabled);
}
this->update();
}
void ListScene::mousePressEvent(QGraphicsSceneMouseEvent* mouseEvent) {
const auto button = mouseEvent->button();
if (this->selectedItem != nullptr) {
this->selectedItem->selected = false;
this->selectedItem->update();
this->selectedItem = nullptr;
}
const auto mousePosition = mouseEvent->buttonDownScenePos(button);
const auto items = this->items(mousePosition);
if (items.empty()) {
return;
}
auto* clickedListItem = dynamic_cast<ListItem*>(items.first());
if (clickedListItem == nullptr) {
return;
}
this->selectedItem = clickedListItem;
clickedListItem->selected = true;
clickedListItem->update();
emit this->selectionChanged(this->selectedItem);
emit this->itemClicked(clickedListItem);
}
void ListScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* mouseEvent) {
if (mouseEvent->button() != Qt::MouseButton::LeftButton) {
return;
}
const auto mousePosition = mouseEvent->buttonDownScenePos(Qt::MouseButton::LeftButton);
const auto items = this->items(mousePosition);
if (items.empty()) {
return;
}
auto* clickedListItem = dynamic_cast<ListItem*>(items.first());
if (clickedListItem == nullptr) {
return;
}
emit this->itemDoubleClicked(clickedListItem);
}
void ListScene::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) {
const auto items = this->items(event->scenePos());
if (items.empty()) {
return;
}
auto* listItem = dynamic_cast<ListItem*>(items.first());
if (listItem == nullptr) {
return;
}
emit this->itemContextMenu(listItem, event->screenPos());
}
}

View File

@@ -0,0 +1,60 @@
#pragma once
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QMargins>
#include <vector>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsSceneWheelEvent>
#include <QGraphicsSceneContextMenuEvent>
#include <QKeyEvent>
#include <QGraphicsRectItem>
#include <QPointF>
#include "ListItem.hpp"
namespace Bloom::Widgets
{
class ListScene: public QGraphicsScene
{
Q_OBJECT
public:
QMargins margins = QMargins(0, 0, 0, 10);
ListScene(
std::vector<ListItem*> items,
QGraphicsView* parent
);
void refreshGeometry();
void setEnabled(bool enabled);
signals:
void selectionChanged(ListItem* selectedItem);
void itemClicked(ListItem* item);
void itemDoubleClicked(ListItem* item);
void itemContextMenu(ListItem* item, QPoint sourcePosition);
protected:
void mousePressEvent(QGraphicsSceneMouseEvent* mouseEvent) override;
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent* mouseEvent) override;
void contextMenuEvent(QGraphicsSceneContextMenuEvent* event) override;
private:
const std::vector<ListItem*> listItems;
QGraphicsView* const parent;
bool enabled = false;
ListItem* selectedItem = nullptr;
QWidget* viewport() const {
const auto views = this->views();
if (!views.empty()) {
return views.first()->viewport();
}
return nullptr;
}
};
}

View File

@@ -0,0 +1,40 @@
#include "ListView.hpp"
namespace Bloom::Widgets
{
ListView::ListView(
const std::vector<ListItem*>& items,
QWidget* parent
)
: QGraphicsView(parent)
{
this->setObjectName("list-graphics-view");
this->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents);
this->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOn);
this->setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff);
this->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
this->setViewportUpdateMode(QGraphicsView::MinimalViewportUpdate);
this->setOptimizationFlag(QGraphicsView::DontSavePainterState, true);
this->setOptimizationFlag(QGraphicsView::DontAdjustForAntialiasing, true);
this->setCacheMode(QGraphicsView::CacheModeFlag::CacheNone);
this->setFocusPolicy(Qt::StrongFocus);
this->scene = new ListScene(std::move(items), this);
this->setScene(this->scene);
}
bool ListView::event(QEvent* event) {
const auto eventType = event->type();
if (eventType == QEvent::Type::EnabledChange) {
this->scene->setEnabled(this->isEnabled());
}
return QGraphicsView::event(event);
}
void ListView::resizeEvent(QResizeEvent* event) {
QGraphicsView::resizeEvent(event);
this->scene->refreshGeometry();
}
}

View File

@@ -0,0 +1,32 @@
#pragma once
#include <QGraphicsView>
#include <vector>
#include <QEvent>
#include "ListScene.hpp"
#include "ListItem.hpp"
namespace Bloom::Widgets
{
class ListView: public QGraphicsView
{
Q_OBJECT
public:
ListView(
const std::vector<ListItem*>& items,
QWidget* parent
);
ListScene* listScene() const {
return this->scene;
}
protected:
ListScene* scene = nullptr;
bool event(QEvent* event) override;
void resizeEvent(QResizeEvent* event) override;
};
}