diff --git a/editor/resources/styles/ModulePreviewList.qss b/editor/resources/styles/ModulePreviewList.qss index 4db714a0..a04682e9 100644 --- a/editor/resources/styles/ModulePreviewList.qss +++ b/editor/resources/styles/ModulePreviewList.qss @@ -6,11 +6,11 @@ margin: 5px 0 5px 10px; } -QGraphicsView { +#image { border: 2px solid #222; border-radius: 5px; } -QGraphicsView:hover { +#image:hover { border-color: #444; } diff --git a/editor/widgets/modulebrowser/CMakeLists.txt b/editor/widgets/modulebrowser/CMakeLists.txt index 18c45620..0fa80f93 100644 --- a/editor/widgets/modulebrowser/CMakeLists.txt +++ b/editor/widgets/modulebrowser/CMakeLists.txt @@ -4,7 +4,6 @@ set(SOURCE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/ModuleBrowserPanel.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/ModulePreviewButton.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/ModulePreviewCanvas.cpp" - "${CMAKE_CURRENT_SOURCE_DIR}/ModulePreviewList.cpp" - "${CMAKE_CURRENT_SOURCE_DIR}/ModulePreviewView.cpp") + "${CMAKE_CURRENT_SOURCE_DIR}/ModulePreviewList.cpp") target_sources(axiom_widgets PRIVATE ${SOURCE_FILES}) diff --git a/editor/widgets/modulebrowser/ModulePreviewButton.cpp b/editor/widgets/modulebrowser/ModulePreviewButton.cpp index 16694d75..41c07182 100644 --- a/editor/widgets/modulebrowser/ModulePreviewButton.cpp +++ b/editor/widgets/modulebrowser/ModulePreviewButton.cpp @@ -1,17 +1,30 @@ #include "ModulePreviewButton.h" +#include #include +#include +#include +#include #include +#include +#include -#include "ModulePreviewView.h" +#include "../node/NodeItem.h" +#include "../windows/MainWindow.h" +#include "../windows/ModulePropertiesWindow.h" +#include "ModulePreviewCanvas.h" #include "editor/model/Library.h" #include "editor/model/LibraryEntry.h" +#include "editor/model/objects/RootSurface.h" +#include "editor/model/serialize/LibrarySerializer.h" +#include "editor/model/serialize/ModelObjectSerializer.h" +#include "editor/model/serialize/ProjectSerializer.h" using namespace AxiomGui; ModulePreviewButton::ModulePreviewButton(MainWindow *window, AxiomModel::Library *library, AxiomModel::LibraryEntry *entry, QWidget *parent) - : QFrame(parent), library(library), _entry(entry) { + : QFrame(parent), window(window), library(library), _entry(entry) { setFixedWidth(110); auto mainLayout = new QGridLayout(this); @@ -19,9 +32,14 @@ ModulePreviewButton::ModulePreviewButton(MainWindow *window, AxiomModel::Library mainLayout->setContentsMargins(0, 0, 0, 0); mainLayout->setRowStretch(0, 1); - mainLayout->addWidget(new ModulePreviewView(window, library, entry, this), 0, 0); + image = new QLabel(this); + image->setObjectName("image"); + mainLayout->addWidget(image, 0, 0); + image->setMargin(0); + image->setContentsMargins(0, 0, 0, 0); label = new QLabel(this); + label->setObjectName("label"); mainLayout->addWidget(label, 1, 0); label->setMargin(0); label->setContentsMargins(5, 0, 0, 0); @@ -33,9 +51,114 @@ ModulePreviewButton::ModulePreviewButton(MainWindow *window, AxiomModel::Library library->activeTagChanged.connect(this, &ModulePreviewButton::updateIsVisible); library->activeSearchChanged.connect(this, &ModulePreviewButton::updateIsVisible); + + entry->root()->history().stackChanged.connect(this, &ModulePreviewButton::updateImage); + + updateImage(); updateIsVisible(); } +void ModulePreviewButton::mousePressEvent(QMouseEvent *event) { + QFrame::mousePressEvent(event); + + if (event->button() == Qt::LeftButton) { + event->accept(); + auto drag = new QDrag(this); + + // serialize the surface and set mime data + auto centerPos = AxiomModel::GridSurface::findCenter(_entry->rootSurface()->grid().items().sequence()); + QByteArray serializeArray; + QDataStream stream(&serializeArray, QIODevice::WriteOnly); + stream << centerPos; + AxiomModel::ModelObjectSerializer::serializeChunk( + stream, _entry->rootSurface()->uuid(), + AxiomModel::findDependents( + AxiomCommon::dynamicCast(_entry->root()->pool().sequence().sequence()), + _entry->rootSurface()->uuid(), false)); + + auto mimeData = new QMimeData(); + mimeData->setData("application/axiom-partial-surface", serializeArray); + drag->setMimeData(mimeData); + + drag->exec(); + } +} + +void ModulePreviewButton::mouseDoubleClickEvent(QMouseEvent *event) { + window->showSurface(nullptr, _entry->rootSurface(), true, false); +} + +void ModulePreviewButton::contextMenuEvent(QContextMenuEvent *event) { + event->accept(); + + QMenu menu; + auto editAction = menu.addAction("&Edit"); + auto propertiesAction = menu.addAction("&Properties..."); + menu.addSeparator(); + auto exportAction = menu.addAction("E&xport..."); + menu.addSeparator(); + auto deleteAction = menu.addAction("&Delete"); + + auto selectedAction = menu.exec(event->globalPos()); + if (selectedAction == editAction) { + window->showSurface(nullptr, _entry->rootSurface(), true, false); + } else if (selectedAction == propertiesAction) { + ModulePropertiesWindow propWindow(library); + propWindow.setEnteredName(_entry->name()); + + QStringList currentTags; + for (const auto &tag : _entry->tags()) { + currentTags.push_back(tag); + } + propWindow.setEnteredTags(currentTags); + + if (propWindow.exec() == QDialog::Accepted) { + _entry->setName(propWindow.enteredName()); + + auto enteredTags = propWindow.enteredTags(); + std::set newTags(enteredTags.begin(), enteredTags.end()); + + // remove old tags + std::set oldTags(_entry->tags()); + for (const auto &oldTag : oldTags) { + if (newTags.find(oldTag) == newTags.end()) _entry->removeTag(oldTag); + } + + // add new tags + for (const auto &newTag : newTags) { + _entry->addTag(newTag); + } + } + } else if (selectedAction == exportAction) { + auto selectedFile = QFileDialog::getSaveFileName(this, "Export Module", QString(), + tr("Axiom Library Files (*.axl);;All Files (*.*)")); + if (selectedFile.isNull()) return; + + QFile file(selectedFile); + if (!file.open(QIODevice::WriteOnly)) { + QMessageBox(QMessageBox::Critical, "Failed to export module", "The file you selected couldn't be opened.", + QMessageBox::Ok) + .exec(); + return; + } + + QDataStream stream(&file); + AxiomModel::ProjectSerializer::writeHeader(stream, AxiomModel::ProjectSerializer::librarySchemaMagic); + AxiomModel::LibrarySerializer::serializeEntries(1, &_entry, &_entry + 1, stream); + file.close(); + } else if (selectedAction == deleteAction) { + QMessageBox confirmBox(QMessageBox::Warning, "Confirm Delete", + "Are you sure you want to delete this module?\n\n" + "This operation cannot be undone."); + confirmBox.setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel); + confirmBox.setDefaultButton(QMessageBox::Yes); + + if (confirmBox.exec() == QMessageBox::Yes) { + _entry->remove(); + } + } +} + void ModulePreviewButton::setName(QString name) { QFontMetrics metrics(label->font()); auto elidedText = metrics.elidedText(name, Qt::ElideRight, label->width()); @@ -48,3 +171,35 @@ void ModulePreviewButton::updateIsVisible() { library->activeSearch() == "" || _entry->name().contains(library->activeSearch(), Qt::CaseInsensitive); setVisible(hasTag && hasSearch); } + +void ModulePreviewButton::updateImage() { + ModulePreviewCanvas canvas(_entry->rootSurface()); + QGraphicsView view(&canvas); + view.setInteractive(false); + view.setRenderHint(QPainter::Antialiasing); + view.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + view.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + view.setFixedSize(100, 100); + view.setStyleSheet("* { border: none; }"); + + // figure out the bounding box size of the scene + QRectF boundingRect; + for (const auto &item : view.scene()->items()) { + if (auto node = dynamic_cast(item)) { + auto br = node->drawBoundingRect(); + br.moveTopLeft(node->scenePos()); + boundingRect = boundingRect.united(br); + } + } + view.setSceneRect(boundingRect); + + // figure out correct scaling with some padding + auto viewWidth = view.width() - 30; + auto viewHeight = view.height() - 30; + auto scaleFactor = boundingRect.width() > boundingRect.height() ? viewWidth / boundingRect.width() + : viewHeight / boundingRect.height(); + view.centerOn(boundingRect.center()); + view.scale(scaleFactor, scaleFactor); + + image->setPixmap(view.grab()); +} diff --git a/editor/widgets/modulebrowser/ModulePreviewButton.h b/editor/widgets/modulebrowser/ModulePreviewButton.h index a31e4da1..5f652b6b 100644 --- a/editor/widgets/modulebrowser/ModulePreviewButton.h +++ b/editor/widgets/modulebrowser/ModulePreviewButton.h @@ -24,13 +24,24 @@ namespace AxiomGui { AxiomModel::LibraryEntry *entry() { return _entry; } + protected: + void mousePressEvent(QMouseEvent *event) override; + + void mouseDoubleClickEvent(QMouseEvent *event) override; + + void contextMenuEvent(QContextMenuEvent *event) override; + private: + MainWindow *window; AxiomModel::Library *library; AxiomModel::LibraryEntry *_entry; + QLabel *image; QLabel *label; void setName(QString name); void updateIsVisible(); + + void updateImage(); }; } diff --git a/editor/widgets/modulebrowser/ModulePreviewCanvas.cpp b/editor/widgets/modulebrowser/ModulePreviewCanvas.cpp index b7f54c77..e65ad22e 100644 --- a/editor/widgets/modulebrowser/ModulePreviewCanvas.cpp +++ b/editor/widgets/modulebrowser/ModulePreviewCanvas.cpp @@ -19,19 +19,9 @@ ModulePreviewCanvas::ModulePreviewCanvas(NodeSurface *surface) { for (const auto &connection : surface->connections().sequence()) { connection->wire().then(this, [this](std::unique_ptr &wire) { addWire(wire.get()); }); } - - // connect to model - surface->nodes().events().itemAdded().connect(this, &ModulePreviewCanvas::addNode); - surface->connections().events().itemAdded().connect(this, [this](Connection *connection) { - connection->wire().then(this, [this](std::unique_ptr &wire) { addWire(wire.get()); }); - }); } void ModulePreviewCanvas::addNode(AxiomModel::Node *node) { - node->posChanged.connect(this, &ModulePreviewCanvas::contentChanged); - node->sizeChanged.connect(this, &ModulePreviewCanvas::contentChanged); - node->removed.connect(this, &ModulePreviewCanvas::contentChanged); - auto item = new NodeItem(node, nullptr); item->setZValue(1); addItem(item); diff --git a/editor/widgets/modulebrowser/ModulePreviewCanvas.h b/editor/widgets/modulebrowser/ModulePreviewCanvas.h index c3c9ef7d..3be63b69 100644 --- a/editor/widgets/modulebrowser/ModulePreviewCanvas.h +++ b/editor/widgets/modulebrowser/ModulePreviewCanvas.h @@ -20,10 +20,6 @@ namespace AxiomGui { public: explicit ModulePreviewCanvas(AxiomModel::NodeSurface *surface); - signals: - - void contentChanged(); - private slots: void addNode(AxiomModel::Node *node); diff --git a/editor/widgets/modulebrowser/ModulePreviewView.cpp b/editor/widgets/modulebrowser/ModulePreviewView.cpp deleted file mode 100644 index 9e65b117..00000000 --- a/editor/widgets/modulebrowser/ModulePreviewView.cpp +++ /dev/null @@ -1,166 +0,0 @@ -#include "ModulePreviewView.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "../node/NodeItem.h" -#include "../windows/MainWindow.h" -#include "../windows/ModulePropertiesWindow.h" -#include "ModulePreviewCanvas.h" -#include "editor/model/LibraryEntry.h" -#include "editor/model/PoolOperators.h" -#include "editor/model/grid/GridItem.h" -#include "editor/model/objects/RootSurface.h" -#include "editor/model/serialize/LibrarySerializer.h" -#include "editor/model/serialize/ModelObjectSerializer.h" -#include "editor/model/serialize/ProjectSerializer.h" - -using namespace AxiomGui; - -ModulePreviewView::ModulePreviewView(MainWindow *window, AxiomModel::Library *library, AxiomModel::LibraryEntry *entry, - QWidget *parent) - : QGraphicsView(parent), window(window), library(library), entry(entry) { - auto moduleScene = new ModulePreviewCanvas(entry->rootSurface()); - setScene(moduleScene); - scene()->setParent(this); - - setInteractive(false); - setRenderHint(QPainter::Antialiasing); - setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - - setFixedSize(100, 100); - updateScaling(); - connect(moduleScene, &ModulePreviewCanvas::contentChanged, this, &ModulePreviewView::updateScaling); -} - -void ModulePreviewView::mousePressEvent(QMouseEvent *event) { - QGraphicsView::mousePressEvent(event); - - if (event->button() == Qt::LeftButton) { - event->accept(); - auto drag = new QDrag(this); - - // serialize the surface and set mime data - auto centerPos = AxiomModel::GridSurface::findCenter(entry->rootSurface()->grid().items().sequence()); - QByteArray serializeArray; - QDataStream stream(&serializeArray, QIODevice::WriteOnly); - stream << centerPos; - AxiomModel::ModelObjectSerializer::serializeChunk( - stream, entry->rootSurface()->uuid(), - AxiomModel::findDependents( - AxiomCommon::dynamicCast(entry->root()->pool().sequence().sequence()), - entry->rootSurface()->uuid(), false)); - - auto mimeData = new QMimeData(); - mimeData->setData("application/axiom-partial-surface", serializeArray); - drag->setMimeData(mimeData); - - drag->exec(); - } -} - -void ModulePreviewView::mouseDoubleClickEvent(QMouseEvent *) { - window->showSurface(nullptr, entry->rootSurface(), true, false); -} - -void ModulePreviewView::contextMenuEvent(QContextMenuEvent *event) { - event->accept(); - - QMenu menu; - - auto editAction = menu.addAction("&Edit"); - auto propertiesAction = menu.addAction("&Properties..."); - menu.addSeparator(); - auto exportAction = menu.addAction("E&xport..."); - menu.addSeparator(); - auto deleteAction = menu.addAction("&Delete"); - auto selectedAction = menu.exec(event->globalPos()); - - if (selectedAction == editAction) { - window->showSurface(nullptr, entry->rootSurface(), true, false); - } else if (selectedAction == propertiesAction) { - ModulePropertiesWindow propWindow(library); - propWindow.setEnteredName(entry->name()); - - QStringList currentTags; - for (const auto &tag : entry->tags()) { - currentTags.push_back(tag); - } - propWindow.setEnteredTags(currentTags); - - if (propWindow.exec() == QDialog::Accepted) { - entry->setName(propWindow.enteredName()); - - auto enteredTags = propWindow.enteredTags(); - std::set newTags(enteredTags.begin(), enteredTags.end()); - - // remove old tags - std::set oldTags(entry->tags()); - for (const auto &oldTag : oldTags) { - if (newTags.find(oldTag) == newTags.end()) entry->removeTag(oldTag); - } - - // add new tags - for (const auto &newTag : newTags) { - entry->addTag(newTag); - } - } - } else if (selectedAction == exportAction) { - auto selectedFile = QFileDialog::getSaveFileName(this, "Export Module", QString(), - tr("Axiom Library Files (*.axl);;All Files (*.*)")); - if (selectedFile.isNull()) return; - - QFile file(selectedFile); - if (!file.open(QIODevice::WriteOnly)) { - QMessageBox(QMessageBox::Critical, "Failed to export module", "The file you selected couldn't be opened.", - QMessageBox::Ok) - .exec(); - return; - } - - QDataStream stream(&file); - AxiomModel::ProjectSerializer::writeHeader(stream, AxiomModel::ProjectSerializer::librarySchemaMagic); - AxiomModel::LibrarySerializer::serializeEntries(1, &entry, &entry + 1, stream); - file.close(); - } else if (selectedAction == deleteAction) { - QMessageBox confirmBox(QMessageBox::Warning, "Confirm Delete", - "Are you sure you want to delete this module?\n\n" - "This operation cannot be undone."); - confirmBox.setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel); - confirmBox.setDefaultButton(QMessageBox::Yes); - - if (confirmBox.exec() == QMessageBox::Yes) { - entry->remove(); - } - } -} - -void ModulePreviewView::updateScaling() { - // figure out the bounding box size of the scene - QRectF boundingRect; - for (const auto &item : scene()->items()) { - if (auto node = dynamic_cast(item)) { - auto br = node->drawBoundingRect(); - br.setTopLeft(node->scenePos()); - boundingRect = boundingRect.united(br); - } - } - setSceneRect(boundingRect); - - // figure out correct scaling with some padding - auto selfWidth = width() - 30; - auto selfHeight = height() - 30; - auto scaleFactor = boundingRect.width() > boundingRect.height() ? selfWidth / boundingRect.width() - : selfHeight / boundingRect.height(); - - scale(1 / currentScale, 1 / currentScale); - centerOn(boundingRect.center()); - scale(scaleFactor, scaleFactor); - currentScale = scaleFactor; -} diff --git a/editor/widgets/modulebrowser/ModulePreviewView.h b/editor/widgets/modulebrowser/ModulePreviewView.h deleted file mode 100644 index 6bb60121..00000000 --- a/editor/widgets/modulebrowser/ModulePreviewView.h +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#include - -namespace AxiomModel { - class Library; - - class LibraryEntry; -} - -namespace AxiomGui { - - class MainWindow; - - class ModulePreviewView : public QGraphicsView { - Q_OBJECT - - public: - explicit ModulePreviewView(MainWindow *window, AxiomModel::Library *library, AxiomModel::LibraryEntry *entry, - QWidget *parent = nullptr); - - protected: - void mousePressEvent(QMouseEvent *event) override; - - void mouseDoubleClickEvent(QMouseEvent *event) override; - - void contextMenuEvent(QContextMenuEvent *event) override; - - private slots: - - void updateScaling(); - - private: - - MainWindow *window; - AxiomModel::Library *library; - AxiomModel::LibraryEntry *entry; - qreal currentScale = 1; - }; - -}