diff --git a/src/Insight/CMakeLists.txt b/src/Insight/CMakeLists.txt index fa87c7bf..ef431021 100755 --- a/src/Insight/CMakeLists.txt +++ b/src/Insight/CMakeLists.txt @@ -20,6 +20,10 @@ target_sources( ${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/ExpandingHeightScrollAreaWidget.hpp ${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/PushButton.cpp + # ListView widget + ${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/ListView/ListView.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/ListView/ListScene.cpp + # Insight worker tasks ${CMAKE_CURRENT_SOURCE_DIR}/InsightWorker/Tasks/InsightWorkerTask.cpp ${CMAKE_CURRENT_SOURCE_DIR}/InsightWorker/Tasks/ReadTargetRegisters.cpp diff --git a/src/Insight/UserInterfaces/InsightWindow/Stylesheets/Global.qss b/src/Insight/UserInterfaces/InsightWindow/Stylesheets/Global.qss index fcc55e8e..de4cc427 100644 --- a/src/Insight/UserInterfaces/InsightWindow/Stylesheets/Global.qss +++ b/src/Insight/UserInterfaces/InsightWindow/Stylesheets/Global.qss @@ -220,3 +220,8 @@ QCheckBox::indicator:unchecked { margin-top: 2px; image: url(":/compiled/src/Insight/UserInterfaces/InsightWindow/Images/unchecked-action-icon.svg"); } + +#list-graphics-view { + background-color: transparent; + border: none; +} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/ListView/ListItem.hpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/ListView/ListItem.hpp new file mode 100644 index 00000000..01a8961e --- /dev/null +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/ListView/ListItem.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +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; + }; +} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/ListView/ListScene.cpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/ListView/ListScene.cpp new file mode 100644 index 00000000..84aa86c4 --- /dev/null +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/ListView/ListScene.cpp @@ -0,0 +1,129 @@ +#include "ListScene.hpp" + +#include +#include +#include +#include +#include + +namespace Bloom::Widgets +{ + ListScene::ListScene( + std::vector 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(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(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(items.first()); + if (listItem == nullptr) { + return; + } + + emit this->itemContextMenu(listItem, event->screenPos()); + } +} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/ListView/ListScene.hpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/ListView/ListScene.hpp new file mode 100644 index 00000000..d0c21b9a --- /dev/null +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/ListView/ListScene.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ListItem.hpp" + +namespace Bloom::Widgets +{ + class ListScene: public QGraphicsScene + { + Q_OBJECT + + public: + QMargins margins = QMargins(0, 0, 0, 10); + + ListScene( + std::vector 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 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; + } + }; +} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/ListView/ListView.cpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/ListView/ListView.cpp new file mode 100644 index 00000000..2fdddd2b --- /dev/null +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/ListView/ListView.cpp @@ -0,0 +1,40 @@ +#include "ListView.hpp" + +namespace Bloom::Widgets +{ + ListView::ListView( + const std::vector& 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(); + } +} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/ListView/ListView.hpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/ListView/ListView.hpp new file mode 100644 index 00000000..b8fce7c9 --- /dev/null +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/ListView/ListView.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include + +#include "ListScene.hpp" +#include "ListItem.hpp" + +namespace Bloom::Widgets +{ + class ListView: public QGraphicsView + { + Q_OBJECT + + public: + ListView( + const std::vector& items, + QWidget* parent + ); + + ListScene* listScene() const { + return this->scene; + } + + protected: + ListScene* scene = nullptr; + + bool event(QEvent* event) override; + void resizeEvent(QResizeEvent* event) override; + }; +}