summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLaszlo Agocs <laszlo.agocs@qt.io>2017-01-17 11:37:02 +0100
committerLaszlo Agocs <laszlo.agocs@qt.io>2017-01-24 16:23:20 +0000
commit962130c66c9c8d449769d15710595482632b82f5 (patch)
tree9927c4487a1e195a1f963c408566e2f197a79396
parent139d2e5f1024fe940285e897aae130f3d1c18b86 (diff)
Add manual test for the QtGui triangulator
Have a widget-based application that uses qTriangulate for fills and QTriangulatingStroker (and optionally the dash stroke processor) for strokes. The resulting triangle (strip) set is visualized on a simple QPainter canvas, offering the ability to zoom in and examine how the triangulator behaves on a number of example shapes. It is also possible to step through and only have the first N set of triangles drawn. Change-Id: I3a27d86d4ea13a63dd4be0fe81dd4b5ed6e4fa75 Reviewed-by: Andy Nichols <andy.nichols@qt.io>
-rw-r--r--tests/manual/triangulator/main.cpp43
-rw-r--r--tests/manual/triangulator/triangulator.pro9
-rw-r--r--tests/manual/triangulator/triviswidget.cpp418
-rw-r--r--tests/manual/triangulator/triviswidget.h139
4 files changed, 609 insertions, 0 deletions
diff --git a/tests/manual/triangulator/main.cpp b/tests/manual/triangulator/main.cpp
new file mode 100644
index 0000000000..066c9f374e
--- /dev/null
+++ b/tests/manual/triangulator/main.cpp
@@ -0,0 +1,43 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QApplication>
+#include <QMainWindow>
+#include "triviswidget.h"
+
+int main(int argc, char *argv[])
+{
+ QApplication app(argc, argv);
+
+ QMainWindow wnd;
+ wnd.resize(1280, 800);
+ wnd.setCentralWidget(new TriangulationVisualizer);
+ wnd.show();
+
+ return app.exec();
+}
diff --git a/tests/manual/triangulator/triangulator.pro b/tests/manual/triangulator/triangulator.pro
new file mode 100644
index 0000000000..95c0b15ee4
--- /dev/null
+++ b/tests/manual/triangulator/triangulator.pro
@@ -0,0 +1,9 @@
+TEMPLATE = app
+TARGET = triangulator
+
+QT += gui-private widgets
+
+SOURCES += main.cpp \
+ triviswidget.cpp
+
+HEADERS += triviswidget.h
diff --git a/tests/manual/triangulator/triviswidget.cpp b/tests/manual/triangulator/triviswidget.cpp
new file mode 100644
index 0000000000..de1efdb4a0
--- /dev/null
+++ b/tests/manual/triangulator/triviswidget.cpp
@@ -0,0 +1,418 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "triviswidget.h"
+#include <QVBoxLayout>
+#include <QHBoxLayout>
+#include <QGroupBox>
+#include <QWheelEvent>
+#include <QScrollBar>
+#include <QPainter>
+#include <QPainterPath>
+#include <QTimer>
+#include <QtGui/private/qtriangulator_p.h>
+#include <QtGui/private/qtriangulatingstroker_p.h>
+#include <QDebug>
+
+static const int W = 100;
+static const int H = 100;
+static const int MAX_ZOOM = 512;
+
+class ScrollArea : public QScrollArea {
+protected:
+ void wheelEvent(QWheelEvent *event) override {
+ if (!event->modifiers().testFlag(Qt::ControlModifier))
+ QScrollArea::wheelEvent(event);
+ }
+};
+
+TriangulationVisualizer::TriangulationVisualizer(QWidget *parent)
+ : QWidget(parent)
+{
+ QVBoxLayout *mainLayout = new QVBoxLayout;
+
+ QHBoxLayout *headerLayout = new QHBoxLayout;
+
+ QGroupBox *cbBox = new QGroupBox(QLatin1String("Shape"));
+ m_cbShape = new QComboBox;
+ QVBoxLayout *cbBoxLayout = new QVBoxLayout;
+ cbBoxLayout->addWidget(m_cbShape);
+ cbBox->setLayout(cbBoxLayout);
+ headerLayout->addWidget(cbBox);
+
+ m_lbPreview = new QLabel;
+ m_lbPreview->setFixedSize(W, H);
+ headerLayout->addWidget(m_lbPreview);
+
+ QGroupBox *typeBox = new QGroupBox(QLatin1String("Type"));
+ m_rdStroke = new QRadioButton(QLatin1String("Stroke"));
+ m_rdStroke->setChecked(true);
+ m_rdFill = new QRadioButton(QLatin1String("Fill"));
+ QVBoxLayout *typeBoxLayout = new QVBoxLayout;
+ typeBoxLayout->addWidget(m_rdStroke);
+ typeBoxLayout->addWidget(m_rdFill);
+ typeBox->setLayout(typeBoxLayout);
+ headerLayout->addWidget(typeBox);
+
+ QGroupBox *paramBox = new QGroupBox(QLatin1String("Stroke params"));
+ QVBoxLayout *paramBoxLayout = new QVBoxLayout;
+ m_spStrokeWidth = new QSpinBox;
+ m_spStrokeWidth->setPrefix(QLatin1String("Stroke width: "));
+ m_spStrokeWidth->setMinimum(1);
+ m_spStrokeWidth->setMaximum(32);
+ m_spStrokeWidth->setValue(1);
+ m_chDash = new QCheckBox(QLatin1String("Dash stroke"));
+ paramBoxLayout->addWidget(m_spStrokeWidth);
+ paramBoxLayout->addWidget(m_chDash);
+ paramBox->setLayout(paramBoxLayout);
+ headerLayout->addWidget(paramBox);
+
+ m_lbInfo = new QLabel;
+ headerLayout->addWidget(m_lbInfo);
+
+ QGroupBox *animBox = new QGroupBox(QLatin1String("Step through"));
+ QVBoxLayout *animBoxLayout = new QVBoxLayout;
+ m_chStepEnable = new QCheckBox(QLatin1String("Enable"));
+ m_spStepStroke = new QSpinBox;
+ m_spStepStroke->setPrefix(QLatin1String("Stroke steps: "));
+ m_spStepStroke->setMinimum(3);
+ m_spStepStroke->setMaximum(INT_MAX);
+ m_spStepStroke->setEnabled(false);
+ m_spStepFill = new QSpinBox;
+ m_spStepFill->setPrefix(QLatin1String("Fill steps: "));
+ m_spStepFill->setMinimum(3);
+ m_spStepFill->setMaximum(INT_MAX);
+ m_spStepFill->setEnabled(false);
+ animBoxLayout->addWidget(m_chStepEnable);
+ animBoxLayout->addWidget(m_spStepStroke);
+ animBoxLayout->addWidget(m_spStepFill);
+ animBox->setLayout(animBoxLayout);
+ headerLayout->addWidget(animBox);
+
+ m_canvas = new TriVisCanvas;
+ m_scrollArea = new ScrollArea;
+ m_scrollArea->setWidget(m_canvas);
+ m_scrollArea->setMinimumSize(W, H);
+
+ mainLayout->addLayout(headerLayout);
+ mainLayout->addWidget(m_scrollArea);
+ mainLayout->setStretchFactor(m_scrollArea, 9);
+
+ setLayout(mainLayout);
+
+ for (const QString &shapeName : m_canvas->shapes())
+ m_cbShape->addItem(shapeName);
+
+ connect(m_cbShape, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), [this]() {
+ m_canvas->setIndex(m_cbShape->currentIndex());
+ m_canvas->retriangulate();
+ });
+ connect(m_rdFill, &QRadioButton::toggled, [this]() {
+ m_canvas->setType(TriVisCanvas::Fill);
+ m_canvas->retriangulate();
+ });
+ connect(m_rdStroke, &QRadioButton::toggled, [this]() {
+ m_canvas->setType(TriVisCanvas::Stroke);
+ m_canvas->retriangulate();
+ });
+
+ connect(m_spStrokeWidth, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [this]() {
+ m_canvas->setStrokeWidth(m_spStrokeWidth->value());
+ m_canvas->regeneratePreviews();
+ m_canvas->retriangulate();
+ });
+
+ connect(m_chDash, &QCheckBox::toggled, [this]() {
+ m_canvas->setDashStroke(m_chDash->isChecked());
+ m_canvas->regeneratePreviews();
+ m_canvas->retriangulate();
+ });
+
+ connect(m_chStepEnable, &QCheckBox::toggled, [this]() {
+ bool enable = m_chStepEnable->isChecked();
+ m_spStepStroke->setEnabled(enable);
+ m_spStepFill->setEnabled(enable);
+ if (enable)
+ m_canvas->setStepLimits(m_spStepStroke->value(), m_spStepFill->value());
+ else
+ m_canvas->setStepLimits(0, 0);
+ });
+
+ connect(m_spStepStroke, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [this]() {
+ m_canvas->setStepLimits(m_spStepStroke->value(), m_spStepFill->value());
+ });
+
+ connect(m_spStepFill, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [this]() {
+ m_canvas->setStepLimits(m_spStepStroke->value(), m_spStepFill->value());
+ });
+
+ connect(m_canvas, &TriVisCanvas::retriangulated, [this]() {
+ updateInfoLabel();
+ updatePreviewLabel();
+ });
+
+ connect(m_canvas, &TriVisCanvas::zoomChanged, [this](float oldZoom, float newZoom) {
+ QScrollBar *sb = m_scrollArea->horizontalScrollBar();
+ float x = sb->value() / oldZoom;
+ sb->setValue(x * newZoom);
+ sb = m_scrollArea->verticalScrollBar();
+ float y = sb->value() / oldZoom;
+ sb->setValue(y * newZoom);
+ updateInfoLabel();
+
+ });
+
+ m_canvas->retriangulate();
+}
+
+void TriangulationVisualizer::updateInfoLabel()
+{
+ m_lbInfo->setText(QString(QStringLiteral("Type: %1\n%2 vertices (x, y)\n%3 indices\nzoom: %4\nUSE CTRL+WHEEL TO ZOOM"))
+ .arg(m_canvas->geomType() == TriVisCanvas::Triangles ? QLatin1String("Triangles") : QLatin1String("Triangle strips"))
+ .arg(m_canvas->vertexCount())
+ .arg(m_canvas->indexCount())
+ .arg(m_canvas->zoomLevel()));
+}
+
+void TriangulationVisualizer::updatePreviewLabel()
+{
+ m_lbPreview->setPixmap(QPixmap::fromImage(m_canvas->preview()).scaled(m_lbPreview->size()));
+}
+
+const int TLX = 10;
+const int TLY = 10;
+
+TriVisCanvas::TriVisCanvas(QWidget *parent)
+ : QWidget(parent)
+{
+ resize(W * m_zoom, H * m_zoom);
+
+ QPainterPath linePath;
+ linePath.moveTo(TLX, TLY);
+ linePath.lineTo(TLX + 30, TLY + 30);
+ m_paths << linePath;
+
+ QPainterPath rectPath;
+ rectPath.moveTo(TLX, TLY);
+ rectPath.lineTo(TLX + 30, TLY);
+ rectPath.lineTo(TLX + 30, TLY + 30);
+ rectPath.lineTo(TLX, TLY + 30);
+ rectPath.lineTo(TLX, TLY);
+ m_paths << rectPath;
+
+ QPainterPath roundRectPath;
+ roundRectPath.addRoundedRect(TLX, TLY, TLX + 29, TLY + 29, 5, 5);
+ m_paths << roundRectPath;
+
+ QPainterPath ellipsePath;
+ ellipsePath.addEllipse(TLX, TLY, 40, 20);
+ m_paths << ellipsePath;
+
+ QPainterPath cubicPath;
+ cubicPath.moveTo(TLX, TLY + 30);
+ cubicPath.cubicTo(15, 2, 40, 40, 30, 10);
+ m_paths << cubicPath;
+
+ QPainterPath cubicPath2;
+ cubicPath2.moveTo(TLX, TLY + 20);
+ cubicPath2.cubicTo(15, 2, 30, 30, 30, 35);
+ m_paths << cubicPath2;
+
+ regeneratePreviews();
+}
+
+QStringList TriVisCanvas::shapes() const
+{
+ return QStringList()
+ << "line"
+ << "rect"
+ << "roundedrect"
+ << "ellipse"
+ << "cubic curve 1"
+ << "cubic curve 2";
+}
+
+void TriVisCanvas::regeneratePreviews()
+{
+ m_strokePreviews.clear();
+ m_fillPreviews.clear();
+ for (int i = 0; i < m_paths.count(); ++i)
+ addPreview(i);
+}
+
+void TriVisCanvas::addPreview(int idx)
+{
+ QPen pen(Qt::black);
+ pen.setWidthF(m_strokeWidth);
+ if (m_dashStroke)
+ pen.setStyle(Qt::DashLine);
+
+ QImage img(W, H, QImage::Format_RGB32);
+ img.fill(Qt::white);
+ QPainter p(&img);
+ p.translate(-TLX, -TLY);
+ p.scale(2, 2);
+ p.strokePath(m_paths[idx], pen);
+ p.end();
+ m_strokePreviews.append(img);
+
+ img = QImage(W, H, QImage::Format_RGB32);
+ img.fill(Qt::white);
+ p.begin(&img);
+ p.translate(-TLX, -TLY);
+ p.scale(2, 2);
+ p.fillPath(m_paths[idx], QBrush(Qt::gray));
+ p.end();
+ m_fillPreviews.append(img);
+}
+
+QImage TriVisCanvas::preview() const
+{
+ if (m_type == Stroke)
+ return m_strokePreviews[m_idx];
+ else
+ return m_fillPreviews[m_idx];
+}
+
+static const qreal SCALE = 100;
+
+void TriVisCanvas::retriangulate()
+{
+ const QPainterPath &path(m_paths[m_idx]);
+
+ if (m_type == Stroke) {
+ const QVectorPath &vp = qtVectorPathForPath(path);
+ const QSize clipSize(W, H);
+ const QRectF clip(QPointF(0, 0), clipSize);
+ const qreal inverseScale = 1.0 / SCALE;
+
+ QTriangulatingStroker stroker;
+ stroker.setInvScale(inverseScale);
+
+ QPen pen;
+ pen.setWidthF(m_strokeWidth);
+ if (m_dashStroke)
+ pen.setStyle(Qt::DashLine);
+
+ if (pen.style() == Qt::SolidLine) {
+ stroker.process(vp, pen, clip, 0);
+ } else {
+ QDashedStrokeProcessor dashStroker;
+ dashStroker.setInvScale(inverseScale);
+ dashStroker.process(vp, pen, clip, 0);
+ QVectorPath dashStroke(dashStroker.points(), dashStroker.elementCount(),
+ dashStroker.elementTypes(), 0);
+ stroker.process(dashStroke, pen, clip, 0);
+ }
+
+ m_strokeVertices.resize(stroker.vertexCount() / 2);
+ if (!m_strokeVertices.isEmpty()) {
+ const float *vsrc = stroker.vertices();
+ for (int i = 0; i < m_strokeVertices.count(); ++i)
+ m_strokeVertices[i].set(vsrc[i * 2], vsrc[i * 2 + 1]);
+ }
+ } else {
+ const QVectorPath &vp = qtVectorPathForPath(path);
+ QTriangleSet ts = qTriangulate(vp, QTransform::fromScale(SCALE, SCALE), 1, true);
+ const int vertexCount = ts.vertices.count() / 2;
+ m_fillVertices.resize(vertexCount);
+ Vertex *vdst = reinterpret_cast<Vertex *>(m_fillVertices.data());
+ const qreal *vsrc = ts.vertices.constData();
+ for (int i = 0; i < vertexCount; ++i)
+ vdst[i].set(vsrc[i * 2] / SCALE, vsrc[i * 2 + 1] / SCALE);
+
+ m_fillIndices.resize(ts.indices.size());
+ if (ts.indices.type() == QVertexIndexVector::UnsignedShort) {
+ const quint16 *shortD = static_cast<const quint16 *>(ts.indices.data());
+ for (int i = 0; i < m_fillIndices.count(); ++i)
+ m_fillIndices[i] = shortD[i];
+ } else {
+ memcpy(m_fillIndices.data(), ts.indices.data(), ts.indices.size() * sizeof(quint32));
+ }
+ }
+
+ emit retriangulated();
+ update();
+}
+
+void TriVisCanvas::paintEvent(QPaintEvent *)
+{
+ QPainter p(this);
+ p.fillRect(rect(), Qt::white);
+
+ if (m_type == Stroke) {
+ QPointF prevPt[3];
+ int cnt = 0;
+ for (int i = 0; i < m_strokeVertices.count() && (!m_strokeStepLimit || i < m_strokeStepLimit); ++i) {
+ auto &v = m_strokeVertices[i];
+ QPointF pt(v.x, v.y);
+ pt *= m_zoom;
+ if (cnt == 1 || cnt == 2)
+ p.drawLine(prevPt[cnt - 1], pt);
+ prevPt[cnt] = pt;
+ cnt = (cnt + 1) % 3;
+ if (!cnt) {
+ p.drawLine(pt, prevPt[cnt]);
+ i -= 2;
+ }
+ }
+ } else {
+ QPointF prevPt[3];
+ int cnt = 0;
+ for (int i = 0; i < m_fillIndices.count() && (!m_fillStepLimit || i < m_fillStepLimit); ++i) {
+ auto &v = m_fillVertices[m_fillIndices[i]];
+ QPointF pt(v.x, v.y);
+ pt *= m_zoom;
+ if (cnt == 1 || cnt == 2)
+ p.drawLine(prevPt[cnt - 1], pt);
+ prevPt[cnt] = pt;
+ cnt = (cnt + 1) % 3;
+ if (!cnt)
+ p.drawLine(pt, prevPt[cnt]);
+ }
+ }
+}
+
+void TriVisCanvas::wheelEvent(QWheelEvent *event)
+{
+ int change = 0;
+
+ if (event->modifiers().testFlag(Qt::ControlModifier)) {
+ if (event->delta() > 0 && m_zoom < MAX_ZOOM) {
+ m_zoom += 1;
+ change = 1;
+ } else if (event->delta() < 0 && m_zoom > 1) {
+ m_zoom -= 1;
+ change = -1;
+ }
+ }
+
+ resize(W * m_zoom, H * m_zoom);
+ emit zoomChanged(m_zoom - change, m_zoom);
+ update();
+}
diff --git a/tests/manual/triangulator/triviswidget.h b/tests/manual/triangulator/triviswidget.h
new file mode 100644
index 0000000000..aee80c6cad
--- /dev/null
+++ b/tests/manual/triangulator/triviswidget.h
@@ -0,0 +1,139 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef TRIVISWIDGET_H
+#define TRIVISWIDGET_H
+
+#include <QWidget>
+#include <QComboBox>
+#include <QRadioButton>
+#include <QLabel>
+#include <QSpinBox>
+#include <QCheckBox>
+#include <QScrollArea>
+
+class TriVisCanvas : public QWidget
+{
+ Q_OBJECT
+
+public:
+ TriVisCanvas(QWidget *parent = nullptr);
+
+ QStringList shapes() const;
+
+ enum Type {
+ Stroke,
+ Fill
+ };
+
+ void setType(Type t) { m_type = t; }
+ void setIndex(int idx) { m_idx = idx; }
+
+ void setStrokeWidth(float w) { m_strokeWidth = w; }
+ void setDashStroke(bool d) { m_dashStroke = d; }
+
+ void setStepLimits(int strokeLimit, int fillLimit) {
+ m_strokeStepLimit = strokeLimit;
+ m_fillStepLimit = fillLimit;
+ update();
+ }
+
+ enum GeomType {
+ Triangles,
+ TriangleStrips
+ };
+
+ QImage preview() const;
+ GeomType geomType() const { return m_type == Stroke ? TriangleStrips : Triangles; }
+ int vertexCount() const { return m_type == Stroke ? m_strokeVertices.count() : m_fillVertices.count(); }
+ int indexCount() const { return m_type == Stroke ? 0 : m_fillIndices.count(); }
+ float zoomLevel() const { return m_zoom; }
+
+ void retriangulate();
+ void regeneratePreviews();
+
+protected:
+ void paintEvent(QPaintEvent *event) override;
+ void wheelEvent(QWheelEvent *event) override;
+
+signals:
+ void retriangulated();
+ void zoomChanged(float oldZoom, float newZoom);
+
+private:
+ void addPreview(int idx);
+
+ Type m_type = Stroke;
+ int m_idx = 0;
+ float m_strokeWidth = 1;
+ bool m_dashStroke = false;
+
+ QVector<QPainterPath> m_paths;
+ QVector<QImage> m_strokePreviews;
+ QVector<QImage> m_fillPreviews;
+
+ struct Vertex {
+ float x, y;
+ void set(float vx, float vy) { x = vx; y = vy; }
+ };
+ QVector<Vertex> m_fillVertices;
+ QVector<quint32> m_fillIndices;
+ QVector<Vertex> m_strokeVertices;
+
+ float m_zoom = 1;
+
+ int m_fillStepLimit = 0;
+ int m_strokeStepLimit = 0;
+};
+
+class TriangulationVisualizer : public QWidget
+{
+ Q_OBJECT
+
+public:
+ TriangulationVisualizer(QWidget *parent = nullptr);
+
+private:
+ void updateInfoLabel();
+ void updatePreviewLabel();
+
+ QComboBox *m_cbShape;
+ QLabel *m_lbPreview;
+ QRadioButton *m_rdStroke;
+ QRadioButton *m_rdFill;
+ QScrollArea *m_scrollArea;
+ TriVisCanvas *m_canvas;
+ QLabel *m_lbInfo;
+ QSpinBox *m_spStrokeWidth;
+ QCheckBox *m_chDash;
+ QCheckBox *m_chStepEnable;
+ QSpinBox *m_spStepStroke;
+ QSpinBox *m_spStepFill;
+};
+
+#endif