Wed, 25 May 2022 17:24:51 +0300
made editing tools not a polymorphic class tree
--- a/CMakeLists.txt Wed May 25 13:49:45 2022 +0300 +++ b/CMakeLists.txt Wed May 25 17:24:51 2022 +0300 @@ -19,15 +19,15 @@ include_directories(${QT_INCLUDES} ${CMAKE_CURRENT_BINARY_DIR}) include_directories(${GLM_INCLUDE_DIR}) add_definitions(-DQT_NO_KEYWORDS) -source_group("1 Foundation code" REGULAR_EXPRESSION "src/.+\\.(cpp|h|ui)") -source_group("4 OpenGL renderer" REGULAR_EXPRESSION "src/gl/.+\\.(cpp|h|ui)") -source_group("5 LDraw line types" REGULAR_EXPRESSION "src/linetypes/.+\\.(cpp|h|ui)") -source_group("5.1 LDraw algorithms" REGULAR_EXPRESSION "src/ldrawalgorithm.(cpp|h|ui)") -source_group("3.2 Widgets" REGULAR_EXPRESSION "src/(ui|widgets)/.+\\.(cpp|h|ui)") -source_group("3.1 Settings editor" REGULAR_EXPRESSION "src/settingseditor/.+\\.(cpp|h|ui)") -source_group("3 User interface" REGULAR_EXPRESSION "src/(mainwindow|document|documentmanager|uiutilities)\\.(cpp|h|ui)") -source_group("2 Model handling" REGULAR_EXPRESSION "src/(model|modeleditor|libraries|colors|parser|vertexmap|edithistory|polygoncache)\\.(cpp|h|ui)") -source_group("6 Editing tools" REGULAR_EXPRESSION "src/tools/.+\\.(cpp|h|ui)") +source_group("LDForge" REGULAR_EXPRESSION "src/.+\\.(cpp|h|ui)") +#source_group("1 Foundation code" REGULAR_EXPRESSION "src/.+\\.(cpp|h|ui)") +#source_group("4 OpenGL renderer" REGULAR_EXPRESSION "src/gl/.+\\.(cpp|h|ui)") +#source_group("5 LDraw line types" REGULAR_EXPRESSION "src/linetypes/.+\\.(cpp|h|ui)") +#source_group("5.1 LDraw algorithms" REGULAR_EXPRESSION "src/ldrawalgorithm.(cpp|h|ui)") +#source_group("3.2 Widgets" REGULAR_EXPRESSION "src/(ui|widgets)/.+\\.(cpp|h|ui)") +#source_group("3.1 Settings editor" REGULAR_EXPRESSION "src/settingseditor/.+\\.(cpp|h|ui)") +#source_group("3 User interface" REGULAR_EXPRESSION "src/(mainwindow|document|documentmanager|uiutilities)\\.(cpp|h|ui)") +#source_group("2 Model handling" REGULAR_EXPRESSION "src/(model|modeleditor|libraries|colors|parser|vertexmap|edithistory|polygoncache)\\.(cpp|h|ui)") set (LDFORGE_SOURCES src/colors.cpp @@ -77,12 +77,6 @@ src/widgets/doublespinbox.cpp src/widgets/matrixeditor.cpp src/widgets/vec3editor.cpp - src/tools/basetool.cpp - src/tools/circletool.cpp - src/tools/selecttool.cpp - src/tools/drawtool.cpp - src/tools/pathtool.cpp - src/tools/transformtool.cpp ) set (LDFORGE_HEADERS src/basics.h @@ -140,12 +134,6 @@ src/widgets/doublespinbox.h src/widgets/matrixeditor.h src/widgets/vec3editor.h - src/tools/selecttool.h - src/tools/basetool.h - src/tools/circletool.h - src/tools/drawtool.h - src/tools/pathtool.h - src/tools/transformtool.h ) set (LDFORGE_FORMS src/document.ui
--- a/src/basics.h Wed May 25 13:49:45 2022 +0300 +++ b/src/basics.h Wed May 25 17:24:51 2022 +0300 @@ -221,8 +221,8 @@ } } -template<typename T = double> -constexpr std::enable_if_t<std::is_floating_point_v<T>, T> PI = static_cast<T>(M_PIl); +template<typename T = float> +constexpr std::enable_if_t<std::is_floating_point_v<T>, T> pi = static_cast<T>(M_PIl); Q_DECLARE_METATYPE(glm::vec3) Q_DECLARE_METATYPE(glm::mat4)
--- a/src/document.cpp Wed May 25 13:49:45 2022 +0300 +++ b/src/document.cpp Wed May 25 17:24:51 2022 +0300 @@ -22,12 +22,7 @@ #include "ui_document.h" #include "model.h" #include "modeleditor.h" -#include "tools/basetool.h" -#include "tools/drawtool.h" -#include "tools/pathtool.h" -#include "tools/selecttool.h" -#include "tools/transformtool.h" -#include "tools/circletool.h" +#include "ui/objecteditor.h" Document::Document( Model* model, @@ -36,12 +31,13 @@ QWidget* parent) : QWidget{parent}, colorTable{colorTable}, + canvas{new Canvas{model, this, documents, colorTable, this}}, model{model}, documents{documents}, vertexMap{model}, - renderer{new Canvas{model, documents, colorTable, this}}, - ui{*new Ui::Document}, - toolsBar{new QToolBar{this}} + ui{*new Ui_Document}, + toolsBar{new QToolBar{this}}, + objectEditor{new ObjectEditor{this}} { this->ui.setupUi(this); const int listWidth = static_cast<int>(this->width() / 3); @@ -50,16 +46,16 @@ this->ui.toolsBarContainer->layout()->addWidget(this->toolsBar); this->ui.listView->setModel(model); this->ui.viewportFrame->setLayout(new QVBoxLayout{this->ui.listView}); - this->ui.viewportFrame->layout()->addWidget(this->renderer); + this->ui.viewportFrame->layout()->addWidget(this->canvas); this->toolsBar->setOrientation(Qt::Vertical); this->setMouseTracking(true); connect(this->ui.viewportListSplitter, &QSplitter::splitterMoved, this, &Document::splitterChanged); - connect(this->renderer, &Canvas::newStatusText, this, &Document::newStatusText); - connect(this->renderer, &Canvas::selectionChanged, [&](const QSet<ldraw::id_t>& newSelection) + connect(this->canvas, &Canvas::newStatusText, this, &Document::newStatusText); + connect(this->canvas, &Canvas::selectionChanged, [&](const QSet<ldraw::id_t>& newSelection) { QItemSelectionModel* selectionModel = this->ui.listView->selectionModel(); QItemSelection selection; - for (ldraw::id_t id : newSelection) + for (const ldraw::id_t id : newSelection) { QModelIndex index = this->model->find(id); if (index != QModelIndex{}) @@ -69,7 +65,6 @@ } QSignalBlocker blocker{this}; selectionModel->select(selection, QItemSelectionModel::ClearAndSelect); - this->selectionChanged(newSelection); }); connect(this->ui.listView->selectionModel(), &QItemSelectionModel::selectionChanged, [&](const QItemSelection& selected, const QItemSelection& deselected) @@ -79,34 +74,12 @@ { return fn::map<QSet<ldraw::id_t>>(selection.indexes(), resolveIndex); }; - this->renderer->handleSelectionChange(resolve(selected), resolve(deselected)); - this->selectionChanged(resolve(this->ui.listView->selectionModel()->selection())); + this->canvas->handleSelectionChange(resolve(selected), resolve(deselected)); }); - connect(this->model, &Model::dataChanged, this->renderer, qOverload<>(&Canvas::update)); - connect(this->renderer, &Canvas::mouseClick, this, [this](Canvas* canvas, QMouseEvent* event) - { - if (this->selectedTool != nullptr) - { - this->selectedTool->mouseClick(canvas, event); - } - }); - connect(this->renderer, &Canvas::mouseMove, this, [this](Canvas* canvas, QMouseEvent* event) - { - if (this->selectedTool != nullptr) - { - this->selectedTool->mouseMove(this, canvas, event); - } - }); + connect(this->model, &Model::dataChanged, this->canvas, qOverload<>(&Canvas::update)); connect(&this->vertexMap, &VertexMap::verticesChanged, [&]() { - this->renderer->rebuildVertices(this); - }); - this->setCanvasOverpaintCallback([&](Canvas* canvas, QPainter* painter) - { - if (this->selectedTool != nullptr) - { - this->selectedTool->overpaint(canvas, painter); - } + this->canvas->rebuildVertices(this); }); this->initializeTools(); } @@ -126,16 +99,6 @@ this->ui.viewportListSplitter->restoreState(state); } -void Document::setRenderPreferences(const gl::RenderPreferences& newPreferences) -{ - this->renderer->setRenderPreferences(newPreferences); -} - -void Document::setCanvasOverpaintCallback(Canvas::OverpaintCallback fn) -{ - this->renderer->setOverpaintCallback(fn); -} - std::unique_ptr<ModelEditor> Document::editModel() { std::unique_ptr<ModelEditor> editorPointer = std::make_unique<ModelEditor>(*this->model); @@ -150,102 +113,62 @@ this->vertexMap.apply(fn); } -void Document::selectionChanged(const QSet<ldraw::id_t>& newSelection) -{ - for (BaseTool* tool : this->tools) - { - tool->selectionChanged(newSelection); - } -} +const char INDEX_PROPERTY[] = "_editing_mode_index"; void Document::initializeTools() { - this->tools.clear(); - this->tools.reserve(3); - this->tools.push_back(new SelectTool{this}); - this->tools.push_back(new DrawTool{this}); - this->tools.push_back(new CircleTool{this}); - this->tools.push_back(new PathTool{this}); - this->tools.push_back(new TransformTool{this}); - for (BaseTool* toolInstance : this->tools) + const struct { - QAction* action = new QAction{toolInstance->name(), this}; + QString name, tooltip; + QPixmap icon; + QWidget* widget; + } editingModesInfo[] = { + { + .name = tr("Select"), + .tooltip = tr("Select elements from the model."), + .icon = {":/icons/navigate-outline.png"}, + .widget = this->objectEditor, + }, + { + .name = tr("Draw"), + .tooltip = tr("Draw new elements into the model."), + .icon = {":/icons/pencil-outline.png"}, + .widget = nullptr, + }, + }; + for (int i = 0; i < countof(editingModesInfo); ++i) { + const auto& editingModeInfo = editingModesInfo[i]; + QAction* action = new QAction{editingModeInfo.name, this}; action->setCheckable(true); - this->toolActions[toolInstance] = action; - action->setToolTip(toolInstance->toolTip()); - action->setIcon(QPixmap{toolInstance->iconName()}); - connect(action, &QAction::triggered, this, &Document::toolActionTriggered); + action->setChecked(i == 0); + action->setToolTip(editingModeInfo.tooltip); + action->setIcon(QPixmap{editingModeInfo.icon}); + action->setProperty(INDEX_PROPERTY, i); this->toolsBar->addAction(action); - QWidget* const widget = toolInstance->toolWidget(); - if (widget) - { - this->ui.toolWidgetStack->addWidget(widget); + QWidget* widget = editingModeInfo.widget; + if (widget == nullptr) { + widget = new QWidget{this}; } - else - { - this->ui.toolWidgetStack->addWidget(new QWidget{this}); - } - connect(toolInstance, &BaseTool::desiredGridChange, this->renderer, &Canvas::setGridMatrix); - } - this->selectTool(this->tools[0]); -} - -void Document::toolActionTriggered() -{ - QAction* action = qobject_cast<QAction*>(sender()); - if (action != nullptr) - { - BaseTool* tool = nullptr; - for (auto&& pair : items(this->toolActions)) - { - if (pair.value == action) - { - tool = pair.key; - } - } - this->selectTool(tool); + this->ui.toolWidgetStack->addWidget(widget); + this->toolActions.push_back(action); + connect(action, &QAction::triggered, this, &Document::editingModeTriggered); } } -void Document::selectTool(BaseTool* tool) +void Document::editingModeTriggered() { - if (tool != nullptr && tool != this->selectedTool) + QAction* triggeredAction = qobject_cast<QAction*>(this->sender()); + if (triggeredAction != nullptr) { - if (this->selectedTool != nullptr) - { - this->selectedTool->reset(); - } - this->selectedTool = tool; - for (auto&& pair : items(this->toolActions)) - { - pair.value->setChecked(pair.key == tool); - } - const int index = this->tools.indexOf(tool); - if (index != -1) - { - this->ui.toolWidgetStack->setCurrentIndex(index); + const int index = triggeredAction->property(INDEX_PROPERTY).toInt(); + this->canvas->mode = static_cast<EditingMode>(index); + this->ui.toolWidgetStack->setCurrentIndex(index); + for (QAction* action : this->toolActions) { + action->setChecked(action == triggeredAction); } } } -void Document::handleKeyPress(QKeyEvent* event) -{ - if (this->selectedTool != nullptr) - { - this->selectedTool->keyReleased(this, this->renderer, event); - } -} - -void Document::adjustGridToView() -{ - this->renderer->adjustGridToView(); -} - -const glm::mat4 &Document::currentGrid() const -{ - return this->renderer->getGridMatrix(); -} - const Model &Document::getModel() const { return *this->model; @@ -253,5 +176,5 @@ const QSet<ldraw::id_t> Document::selectedObjects() const { - return this->renderer->selectedObjects(); + return this->canvas->selectedObjects(); }
--- a/src/document.h Wed May 25 13:49:45 2022 +0300 +++ b/src/document.h Wed May 25 17:24:51 2022 +0300 @@ -25,11 +25,6 @@ #include "vertexmap.h" #include "edithistory.h" -namespace Ui -{ - class Document; -} - class Document : public QWidget { Q_OBJECT @@ -42,35 +37,25 @@ ~Document() override; QByteArray saveSplitterState() const; void restoreSplitterState(const QByteArray& state); - void setRenderPreferences(const gl::RenderPreferences& newPreferences); - void setCanvasOverpaintCallback(Canvas::OverpaintCallback fn); std::unique_ptr<ModelEditor> editModel(); void applyToVertices(VertexMap::ApplyFunction fn) const; - void handleKeyPress(QKeyEvent* event); - void adjustGridToView(); - const glm::mat4& currentGrid() const; const Model& getModel() const; const QSet<ldraw::id_t> selectedObjects() const; const ldraw::ColorTable& colorTable; + Canvas* const canvas; + Q_SLOT void editingModeTriggered(); Q_SIGNALS: void newStatusText(const QString& newStatusText); void splitterChanged(); - void mouseClick(Document* document, Canvas* canvas, QMouseEvent* event); - void mouseMove(Document* document, Canvas* canvas, QMouseEvent* event); private: - void selectionChanged(const QSet<ldraw::id_t>& newSelection); void initializeTools(); - Q_SLOT void toolActionTriggered(); - void selectTool(class BaseTool* tool); Model* model; DocumentManager* const documents; VertexMap vertexMap; - Canvas* renderer; - Ui::Document& ui; + class Ui_Document& ui; QToolBar* toolsBar; - QVector<class BaseTool*> tools; - BaseTool* selectedTool = nullptr; - QMap<BaseTool*, QAction*> toolActions; + std::vector<QAction*> toolActions; + class ObjectEditor* objectEditor; /** * @brief History information of edits to this model */
--- a/src/main.h Wed May 25 13:49:45 2022 +0300 +++ b/src/main.h Wed May 25 17:24:51 2022 +0300 @@ -302,3 +302,14 @@ }); return stream; } + +template<std::size_t N, typename T> +std::array<T, N> vectorToArray(const std::vector<T>& x) +{ + std::array<T, N> result; + for (std::size_t i = 0; i < x.size() and i < N; i += 1) + { + result[i] = x[i]; + } + return result; +}
--- a/src/mainwindow.cpp Wed May 25 13:49:45 2022 +0300 +++ b/src/mainwindow.cpp Wed May 25 17:24:51 2022 +0300 @@ -70,7 +70,7 @@ { if (this->currentDocument() != nullptr) { - this->currentDocument()->adjustGridToView(); + adjustGridToView(this->currentDocument()->canvas); } }); connect(this->ui->actionSave, &QAction::triggered, @@ -194,7 +194,7 @@ void MainWindow::openModelForEditing(const ModelId modelId) { Document* document = new Document{this->documents.getModelById(modelId), &this->documents, this->colorTable}; - document->setRenderPreferences(this->renderPreferences); + document->canvas->setRenderPreferences(this->renderPreferences); connect(document, &Document::newStatusText, [&](const QString& newStatusText) { this->statusBar()->showMessage(newStatusText); @@ -495,7 +495,7 @@ Document* document = qobject_cast<Document*>(this->ui->tabs->widget(i)); if (document != nullptr) { - document->setRenderPreferences(this->renderPreferences); + document->canvas->setRenderPreferences(this->renderPreferences); } } for (auto data : ::renderStyleButtons) @@ -559,11 +559,13 @@ this->colorTable = this->libraries.loadColorTable(errors); } -void MainWindow::keyReleaseEvent(QKeyEvent* event) +void MainWindow::keyReleaseEvent(QKeyEvent* /*event*/) { + /* Document* document = this->currentDocument(); if (document != nullptr) { document->handleKeyPress(event); } + */ }
--- a/src/tools/basetool.cpp Wed May 25 13:49:45 2022 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,14 +0,0 @@ -#include "basetool.h" -#include "document.h" - -BaseTool::BaseTool(Document *document) : - QObject{document}, - parentWidget{document}, - document{document} -{ -} - -QString BaseTool::iconName() const -{ - return ""; -}
--- a/src/tools/basetool.h Wed May 25 13:49:45 2022 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,32 +0,0 @@ -#pragma once -#include <QMouseEvent> -#include "../main.h" -#include "../ui/canvas.h" - -class Document; - -class BaseTool : public QObject -{ - Q_OBJECT - -public: - BaseTool(Document* document); - - virtual QString name() const = 0; - virtual QString toolTip() const = 0; - virtual bool mouseClick(Canvas*, QMouseEvent*) { return false; } - virtual bool mouseDoubleClicked(QMouseEvent*, QMouseEvent*) { return false; } - virtual bool mouseMove(Document*, Canvas*, QMouseEvent*) { return false; } - virtual bool keyReleased(Document*, Canvas*, QKeyEvent*) { return false; } - virtual QWidget* toolWidget() { return nullptr; } - virtual void selectionChanged(const QSet<ldraw::id_t>&) {} - virtual void reset() {} - virtual void overpaint(Canvas*, QPainter*) const {} - virtual QString iconName() const; -Q_SIGNALS: - void desiredGridChange(const glm::mat4& matrix); -protected: - QWidget* const parentWidget; - Document* const document; -}; -
--- a/src/tools/circletool.cpp Wed May 25 13:49:45 2022 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,79 +0,0 @@ -#include "circletool.h" -#include "document.h" - -CircleTool::CircleTool(Document *document) : - AbstractDrawTool{document} -{ -} - -QString CircleTool::name() const -{ - return tr("Circle"); -} - -QString CircleTool::toolTip() const -{ - return tr("Draw circular primitives like circles or discs"); -} - -std::vector<glm::vec3> circle(int divisions) -{ - std::vector<glm::vec3> points; - points.reserve(divisions + 1); - for (int i = 0; i <= divisions; ++i) { - float ang = i * 2.0f * glm::pi<float>() / divisions; - points.push_back({std::sin(ang), std::cos(ang), 0.0f}); - } - return points; -} - -void CircleTool::overpaint(Canvas *canvas, QPainter *painter) const -{ - if (this->previewPolygon.size() >= 2) - { - for (int i : {0, 1}) { - canvas->drawWorldPoint(painter, this->previewPolygon[i]); - } - painter->setPen(QPen{Qt::green, 2, Qt::DashLine, Qt::RoundCap, Qt::MiterJoin}); - canvas->drawWorldPolyline(painter, {this->previewPolygon[0], this->previewPolygon[1]}); - const float size = glm::distance(this->previewPolygon[1], this->previewPolygon[0]); - glm::mat4 matrix = size * this->baseGridMatrix; - matrix[3] = {this->previewPolygon[0], 1}; - std::vector<glm::vec3> points = circle(16); - for (std::size_t i = 0; i < points.size(); ++i) { - points[i] = matrix * glm::vec4{points[i], 1.0f}; - } - painter->setPen(QPen{Qt::black, 2, Qt::DashLine, Qt::RoundCap, Qt::MiterJoin}); - canvas->drawWorldPolyline(painter, points); - } - if (this->previewPolygon.size() >= 3) - { - - } -} - -QString CircleTool::iconName() const -{ - return ":/icons/linetype-circularprimitive.png"; -} - -void CircleTool::addPoint(const glm::vec3 &pos) -{ - AbstractDrawTool::addPoint(pos); - if (this->polygon.size() >= 2) - { - const glm::mat4& grid = this->document->currentGrid(); - const glm::mat4 newGrid = {grid[1], grid[2], grid[0], {this->polygon[0], 1}}; - Q_EMIT this->desiredGridChange(newGrid); - } -} - -void CircleTool::closeShape() -{ - -} - -void CircleTool::reset() -{ - this->baseGridMatrix = this->document->currentGrid(); -}
--- a/src/tools/circletool.h Wed May 25 13:49:45 2022 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,18 +0,0 @@ -#pragma once -#include "drawtool.h" - -class CircleTool : public AbstractDrawTool -{ - Q_OBJECT -public: - Q_INVOKABLE CircleTool(Document* document); - QString name() const override; - QString toolTip() const override; - void overpaint(Canvas *canvas, QPainter *painter) const override; - QString iconName() const override; - void addPoint(const glm::vec3& pos) override; - void closeShape() override; - void reset() override; -private: - glm::mat4 baseGridMatrix; -};
--- a/src/tools/drawtool.cpp Wed May 25 13:49:45 2022 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,219 +0,0 @@ -#include <QMessageBox> -#include <document.h> -#include "linetypes/edge.h" -#include "linetypes/triangle.h" -#include "linetypes/quadrilateral.h" -#include "drawtool.h" -#include "modeleditor.h" - -static const QBrush pointBrush = {Qt::white}; -static const QPen polygonPen = {QBrush{Qt::black}, 2.0, Qt::DashLine}; -static const QPen badPolygonPen = {QBrush{Qt::red}, 2.0, Qt::DashLine}; -static const QPen pointPen = {QBrush{Qt::black}, 2.0}; -static const QBrush greenPolygonBrush = {QColor{64, 255, 128, 192}}; -static const QBrush redPolygonBrush = {QColor{255, 96, 96, 192}}; - -AbstractDrawTool::AbstractDrawTool(Document *document) : - BaseTool{document} -{ -} - -DrawTool::DrawTool(Document* document) : - AbstractDrawTool{document} -{ -} - -QString DrawTool::name() const -{ - static const QString result = tr("Draw"); - return result; -} - -QString DrawTool::toolTip() const -{ - static const QString result = tr("Draw new elements into the model."); - return result; -} - -bool AbstractDrawTool::mouseClick(Canvas* canvas, QMouseEvent* event) -{ - if (event->button() == Qt::LeftButton) - { - this->addCurrentPoint(canvas); - return true; - } - else if (event->button() == Qt::RightButton) - { - this->removeLastPoint(); - return true; - } - else - { - return false; - } -} - -bool AbstractDrawTool::mouseMove(Document* document, Canvas* canvas, QMouseEvent *event) -{ - static_cast<void>(document); - static_cast<void>(event); - const auto& worldPosition = canvas->getWorldPosition(); - if (worldPosition.has_value()) - { - this->previewPoint = worldPosition.value(); - this->updatePreviewPolygon(); - } - return false; -} - -bool AbstractDrawTool::keyReleased(Document*, Canvas* canvas, QKeyEvent* event) -{ - if (event->key() == Qt::Key_Escape) - { - this->polygon.clear(); - this->updatePreviewPolygon(); - canvas->update(); - return true; - } - else - { - return false; - } -} - -void AbstractDrawTool::addCurrentPoint(Canvas* canvas) -{ - const auto& worldPosition = canvas->getWorldPosition(); - if (worldPosition.has_value()) - { - const glm::vec3& pos = worldPosition.value(); - if (this->isCloseToExistingPoints(pos)) - { - this->closeShape(); - } - else - { - this->addPoint(pos); - } - } -} - -void AbstractDrawTool::updatePreviewPolygon() -{ - this->previewPolygon = this->polygon; - this->previewPolygon.resize(this->polygon.size() + 1); - this->previewPolygon.back() = this->previewPoint; - if (this->previewPolygon.size() > 2) - { - this->isconcave = not geom::isConvex(this->previewPolygon); - } -} - -void AbstractDrawTool::reset() -{ - this->polygon.clear(); -} - -void AbstractDrawTool::overpaint(Canvas* canvas, QPainter* painter) const -{ - painter->setPen(this->isconcave ? ::badPolygonPen : ::polygonPen); - if (this->previewPolygon.size() > 2 and not this->isconcave) - { - if (canvas->worldPolygonWinding(this->previewPolygon) == Winding::Clockwise) - { - painter->setBrush(::greenPolygonBrush); - } - else - { - painter->setBrush(::redPolygonBrush); - } - canvas->drawWorldPolygon(painter, this->previewPolygon); - } - else - { - canvas->drawWorldPolyline(painter, this->previewPolygon); - } - painter->setBrush(::pointBrush); - painter->setPen(::pointPen); - for (const glm::vec3& point : this->polygon) - { - canvas->drawWorldPoint(painter, point); - } - canvas->drawWorldPoint(painter, this->previewPoint); -} - -void AbstractDrawTool::addPoint(const glm::vec3 &pos) -{ - this->polygon.push_back(pos); - this->updatePreviewPolygon(); -} - -void AbstractDrawTool::removeLastPoint() -{ - if (this->polygon.size() > 0) - { - this->polygon.erase(this->polygon.end() - 1); - this->updatePreviewPolygon(); - } -} - -void AbstractDrawTool::clearPoints() -{ - this->polygon.clear(); - this->updatePreviewPolygon(); -} - -bool AbstractDrawTool::isCloseToExistingPoints(const glm::vec3 &pos) const -{ - const auto isCloseToPos = [&](const glm::vec3& x) - { - return geom::isclose(x, pos); - }; - return any(this->polygon, isCloseToPos); -} - -QString DrawTool::iconName() const -{ - return ":/icons/pencil-outline.png"; -} - -void DrawTool::addPoint(const glm::vec3 &pos) -{ - AbstractDrawTool::addPoint(pos); - if (this->polygon.size() == 4) - { - this->closeShape(); - } -} - -template<std::size_t N, typename T> -std::array<T, N> vectorToArray(const std::vector<T>& x) -{ - std::array<T, N> result; - for (std::size_t i = 0; i < x.size() and i < N; i += 1) - { - result[i] = x[i]; - } - return result; -} - -void DrawTool::closeShape() -{ - if (this->polygon.size() >= 2 and this->polygon.size() <= 4) - { - std::unique_ptr<ModelEditor> modelEditor = this->document->editModel(); - switch (this->polygon.size()) - { - case 2: - modelEditor->append<ldraw::Edge>(vectorToArray<2>(this->polygon), ldraw::EDGE_COLOR); - break; - case 3: - modelEditor->append<ldraw::Triangle>(vectorToArray<3>(this->polygon), ldraw::MAIN_COLOR); - break; - case 4: - modelEditor->append<ldraw::Quadrilateral>(vectorToArray<4>(this->polygon), ldraw::MAIN_COLOR); - break; - } - } - this->clearPoints(); -}
--- a/src/tools/drawtool.h Wed May 25 13:49:45 2022 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,40 +0,0 @@ -#pragma once -#include "basetool.h" - -class AbstractDrawTool : public BaseTool -{ -public: - AbstractDrawTool(Document* document); - void reset() override; - void overpaint(Canvas*, QPainter*) const override; - bool mouseMove(Document* document, Canvas* canvas, QMouseEvent* event) override; - bool mouseClick(Canvas* canvas, QMouseEvent* event) override; - bool keyReleased(Document*, Canvas* canvas, QKeyEvent* event) override; -protected: - void addCurrentPoint(Canvas *canvas); - virtual void addPoint(const glm::vec3& pos); - virtual void closeShape() = 0; - void removeLastPoint(); - void clearPoints(); - bool isCloseToExistingPoints(const glm::vec3& pos) const; - std::vector<glm::vec3> polygon; - std::vector<glm::vec3> previewPolygon; - glm::vec3 previewPoint; - bool isconcave = false; - void updatePreviewPolygon(); -}; - -class DrawTool : public AbstractDrawTool -{ - Q_OBJECT - -public: - Q_INVOKABLE DrawTool(Document* document); - - QString name() const override; - QString toolTip() const override; - QString iconName() const override; -protected: - virtual void addPoint(const glm::vec3& pos) override; - void closeShape() override; -};
--- a/src/tools/pathtool.cpp Wed May 25 13:49:45 2022 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,48 +0,0 @@ -#include "pathtool.h" -#include "modeleditor.h" -#include "linetypes/edge.h" -#include "document.h" - -PathTool::PathTool(Document *document) : - AbstractDrawTool{document} -{ -} - -QString PathTool::name() const -{ - return tr("Draw path"); -} - -QString PathTool::toolTip() const -{ - return tr("Draw paths"); -} - -void PathTool::overpaint(Canvas* canvas, QPainter* painter) const -{ - painter->setPen(QPen{Qt::black, 2, Qt::DashLine, Qt::RoundCap, Qt::MiterJoin}); - if (this->previewPolygon.size() >= 2) - { - canvas->drawWorldPolyline(painter, this->previewPolygon); - } - for (const glm::vec3& point : this->polygon) - { - canvas->drawWorldPoint(painter, point); - } - canvas->drawWorldPoint(painter, this->previewPoint); -} - -QString PathTool::iconName() const -{ - return ":/icons/polyline.png"; -} - -void PathTool::closeShape() -{ - std::unique_ptr<ModelEditor> modelEditor = this->document->editModel(); - for (std::size_t i = 0; i < this->polygon.size() - 1; i += 1) - { - modelEditor->append<ldraw::Edge>(std::array{this->polygon[i], this->polygon[i + 1]}, ldraw::EDGE_COLOR); - } - this->clearPoints(); -}
--- a/src/tools/pathtool.h Wed May 25 13:49:45 2022 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,15 +0,0 @@ -#pragma once -#include "drawtool.h" - -class PathTool : public AbstractDrawTool -{ - Q_OBJECT - -public: - Q_INVOKABLE PathTool(Document* document); - QString name() const override; - QString toolTip() const override; - void overpaint(Canvas*canvas, QPainter*painter) const override; - QString iconName() const override; - void closeShape() override; -};
--- a/src/tools/selecttool.cpp Wed May 25 13:49:45 2022 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,59 +0,0 @@ -#include "selecttool.h" - -SelectTool::SelectTool(Document* document) : - BaseTool{document}, - objectEditor{new ObjectEditor{document, ldraw::NULL_ID}} -{ -} - -QString SelectTool::name() const -{ - static const QString result = tr("Select"); - return result; -} - -QString SelectTool::toolTip() const -{ - static const QString result = tr("Select elements from the model."); - return result; -} - -bool SelectTool::mouseClick(Canvas* canvas, QMouseEvent* event) -{ - if (event->button() == Qt::LeftButton) - { - const ldraw::id_t highlighted = canvas->getHighlightedObject(); - canvas->clearSelection(); - if (highlighted != ldraw::NULL_ID) - { - canvas->addToSelection(highlighted); - } - return true; - } - else - { - return false; - } -} - -QWidget* SelectTool::toolWidget() -{ - return this->objectEditor; -} - -void SelectTool::selectionChanged(const QSet<ldraw::id_t>& newSelection) -{ - if (newSelection.size() == 1) - { - this->objectEditor->setObjectId(*newSelection.begin()); - } - else - { - this->objectEditor->setObjectId(ldraw::NULL_ID); - } -} - -QString SelectTool::iconName() const -{ - return ":/icons/navigate-outline.png"; -}
--- a/src/tools/selecttool.h Wed May 25 13:49:45 2022 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,18 +0,0 @@ -#pragma once -#include "basetool.h" -#include "ui/objecteditor.h" - -class SelectTool : public BaseTool -{ - Q_OBJECT - -public: - Q_INVOKABLE SelectTool(Document *document); - QString name() const override; - QString toolTip() const override; - bool mouseClick(Canvas*, QMouseEvent*) override; - QWidget* toolWidget() override; - void selectionChanged(const QSet<ldraw::id_t> &newSelection) override; - QString iconName() const override; - ObjectEditor* objectEditor; -};
--- a/src/tools/transformtool.cpp Wed May 25 13:49:45 2022 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,65 +0,0 @@ -#include <QHBoxLayout> -#include "model.h" -#include "modeleditor.h" -#include "linetypes/object.h" -#include "transformtool.h" -#include "document.h" - -TransformTool::TransformTool(Document* document) : - BaseTool{document}, - matrixEditor{new MatrixEditor{document}}, - button{new QPushButton{"Apply", document}}, - widget{new QWidget{document}} -{ - widget->setLayout(new QHBoxLayout{widget}); - widget->layout()->addWidget(this->matrixEditor); - widget->layout()->addWidget(button); - connect(button, &QPushButton::clicked, this, &TransformTool::applyButtonClicked); -} - -QString TransformTool::name() const -{ - return "Transform"; -} - -QString TransformTool::toolTip() const -{ - return "Transforms the selection using a matrix"; -} - -void TransformTool::selectionChanged(const QSet<ldraw::id_t> &newSelection) -{ - this->selection = newSelection; -} - -QWidget* TransformTool::toolWidget() -{ - return this->widget; -} - -QString TransformTool::iconName() const -{ - return ":/icons/grid-outline.png"; -} - -void TransformTool::applyButtonClicked() -{ - std::unique_ptr<ModelEditor> editor = this->document->editModel(); - const glm::mat4 matrix = this->matrixEditor->value(); - for (ldraw::id_t id : this->selection) - { - editor->modifyObject(id, [&](ldraw::Object* object){ - for (int i = 0; i < object->numPoints(); i += 1) - { - const ldraw::Property property = ldraw::pointProperty(i); - const glm::vec3& vec = matrix * glm::vec4{object->getPoint(i), 1}; - object->setProperty({property, QVariant::fromValue(vec)}); - } - QVariant transformMatrix = object->getProperty(ldraw::Property::Transformation); - if (not transformMatrix.isNull()) - { - object->setProperty<ldraw::Property::Transformation>(matrix * transformMatrix.value<glm::mat4>()); - } - }); - } -}
--- a/src/tools/transformtool.h Wed May 25 13:49:45 2022 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -#pragma once -#include <QPushButton> -#include "basetool.h" -#include "widgets/matrixeditor.h" - -class TransformTool : public BaseTool -{ - Q_OBJECT -public: - Q_INVOKABLE TransformTool(Document *document); - virtual QString name() const override; - virtual QString toolTip() const override; - void selectionChanged(const QSet<ldraw::id_t> &newSelection) override; - QWidget *toolWidget() override; - QString iconName() const override; -private: - Q_SLOT void applyButtonClicked(); - MatrixEditor* matrixEditor; - QPushButton* button; - QWidget* widget; - QSet<ldraw::id_t> selection; -};
--- a/src/ui/canvas.cpp Wed May 25 13:49:45 2022 +0300 +++ b/src/ui/canvas.cpp Wed May 25 17:24:51 2022 +0300 @@ -1,13 +1,20 @@ #include <QMouseEvent> #include <QPainter> +#include "modeleditor.h" +#include "document.h" #include "canvas.h" +#include "linetypes/edge.h" +#include "linetypes/triangle.h" +#include "linetypes/quadrilateral.h" Canvas::Canvas( Model* model, + Document *document, DocumentManager* documents, const ldraw::ColorTable& colorTable, QWidget* parent) : - PartRenderer{model, documents, colorTable, parent} + PartRenderer{model, documents, colorTable, parent}, + document{document} { this->setMouseTracking(true); } @@ -39,6 +46,31 @@ } } +void updatePreviewPolygon(DrawState* drawState) +{ + drawState->previewPolygon = drawState->polygon; + drawState->previewPolygon.resize(drawState->polygon.size() + 1); + drawState->previewPolygon.back() = drawState->previewPoint; + if (drawState->previewPolygon.size() > 2) + { + drawState->isconcave = not geom::isConvex(drawState->previewPolygon); + } +} + +void removeLastPoint(DrawState* drawState) +{ + if (drawState->polygon.size() > 0) + { + drawState->polygon.erase(drawState->polygon.end() - 1); + updatePreviewPolygon(drawState); + } +} + +bool isCloseToExistingPoints(const std::vector<glm::vec3>& points, const glm::vec3 &pos) +{ + return any(points, std::bind(geom::isclose, std::placeholders::_1, pos)); +} + void Canvas::mouseMoveEvent(QMouseEvent* event) { const ldraw::id_t id = this->pick(event->pos()); @@ -62,7 +94,21 @@ // grid matrix. this->worldPosition = this->gridMatrix * glm::vec4{*this->worldPosition, 1}; } - Q_EMIT this->mouseMove(this, event); + switch(this->mode) + { + case SelectMode: + break; + case DrawMode: + const auto& worldPosition = this->getWorldPosition(); + if (worldPosition.has_value()) + { + this->drawState.previewPoint = worldPosition.value(); + updatePreviewPolygon(&this->drawState); + this->update(); + } + event->accept(); + break; + } PartRenderer::mouseMoveEvent(event); this->update(); } @@ -78,7 +124,45 @@ { if (this->totalMouseMove < (2.0 / sqrt(2)) * 5.0) { - Q_EMIT this->mouseClick(this, event); + switch(this->mode) + { + case SelectMode: + if (event->button() == Qt::LeftButton) + { + const ldraw::id_t highlighted = this->getHighlightedObject(); + this->clearSelection(); + if (highlighted != ldraw::NULL_ID) + { + this->addToSelection(highlighted); + } + event->accept(); + } + break; + case DrawMode: + if (event->button() == Qt::LeftButton and this->worldPosition.has_value()) + { + const glm::vec3& pos = worldPosition.value(); + if (isCloseToExistingPoints(this->drawState.polygon, pos)) + { + this->closeShape(); + } + else + { + this->drawState.polygon.push_back(pos); + updatePreviewPolygon(&this->drawState); + } + event->accept(); + } + else if (true + and event->button() == Qt::RightButton + and this->drawState.polygon.size() > 0 + ) { + this->drawState.polygon.erase(this->drawState.polygon.end() - 1); + updatePreviewPolygon(&this->drawState); + event->accept(); + } + break; + } } PartRenderer::mouseReleaseEvent(event); this->update(); @@ -114,6 +198,16 @@ this->updateCanvasRenderPreferences(); } +static const struct +{ + const QBrush pointBrush = {Qt::white}; + const QPen polygonPen = {QBrush{Qt::black}, 2.0, Qt::DashLine}; + const QPen badPolygonPen = {QBrush{Qt::red}, 2.0, Qt::DashLine}; + const QPen pointPen = {QBrush{Qt::black}, 2.0}; + const QBrush greenPolygonBrush = {QColor{64, 255, 128, 192}}; + const QBrush redPolygonBrush = {QColor{255, 96, 96, 192}}; +} pens; + void Canvas::paintGL() { PartRenderer::paintGL(); @@ -154,17 +248,44 @@ painter.drawEllipse(pos, 5, 5); painter.drawText(pos + QPointF{5, 5}, vectorToString(*this->worldPosition)); } + QPainter painter{this}; + painter.setRenderHint(QPainter::Antialiasing); + if (this->renderPreferences.drawAxes) { - QPainter painter{this}; - painter.setRenderHint(QPainter::Antialiasing); - if (this->renderPreferences.drawAxes) + this->renderAxesLabels(painter); + } + switch(this->mode) + { + case SelectMode: + break; + case DrawMode: { - this->renderAxesLabels(painter); + painter.setPen(this->drawState.isconcave ? ::pens.badPolygonPen : ::pens.polygonPen); + if (this->drawState.previewPolygon.size() > 2 and not this->drawState.isconcave) + { + if (this->worldPolygonWinding(this->drawState.previewPolygon) == Winding::Clockwise) + { + painter.setBrush(::pens.greenPolygonBrush); + } + else + { + painter.setBrush(::pens.redPolygonBrush); + } + this->drawWorldPolygon(&painter, this->drawState.previewPolygon); + } + else + { + this->drawWorldPolyline(&painter, this->drawState.previewPolygon); + } + painter.setBrush(::pens.pointBrush); + painter.setPen(::pens.pointPen); + for (const glm::vec3& point : this->drawState.polygon) + { + this->drawWorldPoint(&painter, point); + } + this->drawWorldPoint(&painter, this->drawState.previewPoint); } - if (this->overpaintCallback != nullptr) - { - this->overpaintCallback(this, &painter); - } + break; } } } @@ -264,22 +385,20 @@ /** * @brief Adjusts the grid to be so that it is perpendicular to the camera. */ -void Canvas::adjustGridToView() +void adjustGridToView(Canvas* canvas) { - const glm::vec3 cameraDirection = this->cameraVector(); - const glm::vec3 vector_x = glm::normalize(this->gridMatrix * glm::vec4{1, 0, 0, 1}); - const glm::vec3 vector_y = glm::normalize(this->gridMatrix * glm::vec4{0, 1, 0, 1}); + const glm::vec3 cameraDirection = canvas->cameraVector(); + const glm::mat4& grid = canvas->getGridMatrix(); + const glm::vec3 vector_x = glm::normalize(grid * glm::vec4{1, 0, 0, 1}); + const glm::vec3 vector_y = glm::normalize(grid * glm::vec4{0, 1, 0, 1}); const float angle_x = std::abs(glm::dot(vector_x, cameraDirection)); const float angle_y = std::abs(glm::dot(vector_y, cameraDirection)); - if (angle_x < angle_y) - { - this->setGridMatrix(glm::rotate(this->gridMatrix, PI<float> / 2, glm::vec3{1, 0, 0})); - } - else - { - this->setGridMatrix(glm::rotate(this->gridMatrix, PI<float> / 2, glm::vec3{0, 1, 0})); - } - this->update(); + canvas->setGridMatrix(glm::rotate( + grid, + pi<> * 0.5f, + (angle_x < angle_y) ? glm::vec3{1, 0, 0} : glm::vec3{0, 1, 0} + )); + canvas->update(); } /** @@ -295,6 +414,34 @@ return this->gridMatrix; } +void Canvas::closeShape() +{ + if (this->drawState.polygon.size() >= 2 and this->drawState.polygon.size() <= 4) + { + std::unique_ptr<ModelEditor> modelEditor = this->document->editModel(); + switch (this->drawState.polygon.size()) + { + case 2: + modelEditor->append<ldraw::Edge>( + vectorToArray<2>(this->drawState.polygon), + ldraw::EDGE_COLOR); + break; + case 3: + modelEditor->append<ldraw::Triangle>( + vectorToArray<3>(this->drawState.polygon), + ldraw::MAIN_COLOR); + break; + case 4: + modelEditor->append<ldraw::Quadrilateral>( + vectorToArray<4>(this->drawState.polygon), + ldraw::MAIN_COLOR); + break; + } + } + this->drawState.polygon.clear(); + updatePreviewPolygon(&this->drawState); +} + /** * @brief Paints a circle at where @c worldPoint is located on the screen. * @param painter Painter to use to render
--- a/src/ui/canvas.h Wed May 25 13:49:45 2022 +0300 +++ b/src/ui/canvas.h Wed May 25 17:24:51 2022 +0300 @@ -7,6 +7,20 @@ #include "gl/axesprogram.h" #include "gl/vertexprogram.h" +enum EditingMode +{ + SelectMode, + DrawMode +}; + +struct DrawState +{ + std::vector<glm::vec3> polygon; + std::vector<glm::vec3> previewPolygon; + glm::vec3 previewPoint; + bool isconcave = false; +}; + class Canvas : public PartRenderer { Q_OBJECT @@ -14,6 +28,7 @@ using OverpaintCallback = std::function<void(Canvas*, QPainter*)>; Canvas( Model* model, + Document* document, DocumentManager* documents, const ldraw::ColorTable& colorTable, QWidget* parent = nullptr); @@ -24,14 +39,17 @@ void drawWorldPolyline(QPainter* painter, const std::vector<glm::vec3>& points); void drawWorldPolygon(QPainter* painter, const std::vector<glm::vec3>& points); Winding worldPolygonWinding(const std::vector<glm::vec3>& points) const; - const std::optional<glm::vec3>& getWorldPosition() const; - void adjustGridToView(); + const std::optional<glm::vec3>& getWorldPosition() const; const QSet<ldraw::id_t> selectedObjects() const; const glm::mat4& getGridMatrix() const; + void closeShape(); + glm::vec3 cameraVector() const; + DrawState drawState; + EditingMode mode = SelectMode; public Q_SLOTS: void handleSelectionChange(const QSet<ldraw::id_t>& selectedIds, const QSet<ldraw::id_t>& deselectedIds); void rebuildVertices(Document *document); - void setGridMatrix(const glm::mat4 &newMatrix); + void setGridMatrix(const glm::mat4 &newMatrix); protected: void mouseMoveEvent(QMouseEvent* event) override; void mousePressEvent(QMouseEvent* event) override; @@ -44,7 +62,6 @@ void mouseClick(Canvas* canvas, QMouseEvent* event); void mouseMove(Canvas* canvas, QMouseEvent* event); private: - glm::vec3 cameraVector() const; bool isGridPerpendicularToScreen(float threshold) const; QVector<QPointF> convertWorldPointsToScreenPoints(const std::vector<glm::vec3>& worldPoints) const; Q_SLOT void updateCanvasRenderPreferences(); @@ -59,4 +76,7 @@ bool isDark = true; QSet<ldraw::id_t> selection; OverpaintCallback overpaintCallback = nullptr; + Document* document; }; + +void adjustGridToView(Canvas* canvas);