made editing tools not a polymorphic class tree

Wed, 25 May 2022 17:24:51 +0300

author
Teemu Piippo <teemu@hecknology.net>
date
Wed, 25 May 2022 17:24:51 +0300
changeset 191
d355d4c52d51
parent 190
3dbdc243f053
child 192
e6faeffed1d1

made editing tools not a polymorphic class tree

CMakeLists.txt file | annotate | diff | comparison | revisions
src/basics.h file | annotate | diff | comparison | revisions
src/document.cpp file | annotate | diff | comparison | revisions
src/document.h file | annotate | diff | comparison | revisions
src/main.h file | annotate | diff | comparison | revisions
src/mainwindow.cpp file | annotate | diff | comparison | revisions
src/tools/basetool.cpp file | annotate | diff | comparison | revisions
src/tools/basetool.h file | annotate | diff | comparison | revisions
src/tools/circletool.cpp file | annotate | diff | comparison | revisions
src/tools/circletool.h file | annotate | diff | comparison | revisions
src/tools/drawtool.cpp file | annotate | diff | comparison | revisions
src/tools/drawtool.h file | annotate | diff | comparison | revisions
src/tools/pathtool.cpp file | annotate | diff | comparison | revisions
src/tools/pathtool.h file | annotate | diff | comparison | revisions
src/tools/selecttool.cpp file | annotate | diff | comparison | revisions
src/tools/selecttool.h file | annotate | diff | comparison | revisions
src/tools/transformtool.cpp file | annotate | diff | comparison | revisions
src/tools/transformtool.h file | annotate | diff | comparison | revisions
src/ui/canvas.cpp file | annotate | diff | comparison | revisions
src/ui/canvas.h file | annotate | diff | comparison | revisions
--- 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);

mercurial