166 lines
4.5 KiB
C++
166 lines
4.5 KiB
C++
#pragma once
|
|
|
|
#include <QColor>
|
|
#include <QFont>
|
|
#include <QLineF>
|
|
#include <QPainter>
|
|
#include <QPainterPath>
|
|
#include <QPointF>
|
|
#include <QRectF>
|
|
#include <QString>
|
|
#include <QUuid>
|
|
|
|
#include <cmath>
|
|
#include <numbers>
|
|
|
|
namespace ws::model {
|
|
|
|
enum class Tool {
|
|
Select,
|
|
Arrow,
|
|
Line,
|
|
Rectangle,
|
|
Text,
|
|
};
|
|
|
|
enum class AnnotationKind {
|
|
Arrow,
|
|
Line,
|
|
Rectangle,
|
|
Text,
|
|
};
|
|
|
|
struct AnnotationStyle {
|
|
QColor color = QColor("#ff5a36");
|
|
int stroke_width = 4;
|
|
QFont font = QFont(QStringLiteral("Noto Sans"), 18, QFont::DemiBold);
|
|
};
|
|
|
|
struct Annotation {
|
|
QUuid id = QUuid::createUuid();
|
|
AnnotationKind kind = AnnotationKind::Line;
|
|
AnnotationStyle style;
|
|
QLineF line;
|
|
QRectF rect;
|
|
QPointF text_anchor;
|
|
QString text;
|
|
|
|
[[nodiscard]] QRectF bounds() const
|
|
{
|
|
switch (kind) {
|
|
case AnnotationKind::Arrow:
|
|
case AnnotationKind::Line:
|
|
return QRectF(line.p1(), line.p2()).normalized().adjusted(
|
|
-style.stroke_width * 1.5,
|
|
-style.stroke_width * 1.5,
|
|
style.stroke_width * 1.5,
|
|
style.stroke_width * 1.5
|
|
);
|
|
case AnnotationKind::Rectangle:
|
|
return rect.normalized().adjusted(
|
|
-style.stroke_width,
|
|
-style.stroke_width,
|
|
style.stroke_width,
|
|
style.stroke_width
|
|
);
|
|
case AnnotationKind::Text: {
|
|
QFontMetricsF metrics(style.font);
|
|
const QRectF text_rect = metrics.boundingRect(text);
|
|
return QRectF(text_anchor + QPointF(0.0, text_rect.top()), text_rect.size())
|
|
.adjusted(-6.0, -6.0, 6.0, 6.0);
|
|
}
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
void translate(const QPointF& delta)
|
|
{
|
|
switch (kind) {
|
|
case AnnotationKind::Arrow:
|
|
case AnnotationKind::Line:
|
|
line.translate(delta);
|
|
break;
|
|
case AnnotationKind::Rectangle:
|
|
rect.translate(delta);
|
|
break;
|
|
case AnnotationKind::Text:
|
|
text_anchor += delta;
|
|
break;
|
|
}
|
|
}
|
|
|
|
[[nodiscard]] bool hit_test(const QPointF& point, qreal tolerance = 8.0) const
|
|
{
|
|
switch (kind) {
|
|
case AnnotationKind::Arrow:
|
|
case AnnotationKind::Line: {
|
|
QPainterPath path;
|
|
path.moveTo(line.p1());
|
|
path.lineTo(line.p2());
|
|
QPainterPathStroker stroker;
|
|
stroker.setWidth(style.stroke_width + tolerance);
|
|
return stroker.createStroke(path).contains(point);
|
|
}
|
|
case AnnotationKind::Rectangle:
|
|
return bounds().contains(point);
|
|
case AnnotationKind::Text:
|
|
return bounds().contains(point);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void paint(QPainter& painter, bool selected) const
|
|
{
|
|
painter.save();
|
|
|
|
QPen pen(style.color, style.stroke_width, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
|
|
painter.setPen(pen);
|
|
painter.setBrush(Qt::NoBrush);
|
|
|
|
switch (kind) {
|
|
case AnnotationKind::Line:
|
|
painter.drawLine(line);
|
|
break;
|
|
case AnnotationKind::Arrow: {
|
|
painter.drawLine(line);
|
|
|
|
const double angle = std::atan2(-line.dy(), line.dx());
|
|
constexpr qreal head_length = 16.0;
|
|
const QPointF arrow_tip = line.p2();
|
|
const QPointF head_a = arrow_tip + QPointF(
|
|
std::sin(angle + std::numbers::pi_v<double> / 3.0) * head_length,
|
|
std::cos(angle + std::numbers::pi_v<double> / 3.0) * head_length
|
|
);
|
|
const QPointF head_b = arrow_tip + QPointF(
|
|
std::sin(angle + std::numbers::pi_v<double> - std::numbers::pi_v<double> / 3.0) * head_length,
|
|
std::cos(angle + std::numbers::pi_v<double> - std::numbers::pi_v<double> / 3.0) * head_length
|
|
);
|
|
|
|
painter.setBrush(style.color);
|
|
painter.drawPolygon(QPolygonF {arrow_tip, head_a, head_b});
|
|
break;
|
|
}
|
|
case AnnotationKind::Rectangle:
|
|
painter.drawRect(rect.normalized());
|
|
break;
|
|
case AnnotationKind::Text:
|
|
painter.setFont(style.font);
|
|
painter.drawText(text_anchor, text);
|
|
break;
|
|
}
|
|
|
|
if (selected) {
|
|
QPen selection_pen(QColor("#0ea5e9"), 2, Qt::DashLine);
|
|
painter.setPen(selection_pen);
|
|
painter.setBrush(Qt::NoBrush);
|
|
painter.drawRect(bounds());
|
|
}
|
|
|
|
painter.restore();
|
|
}
|
|
};
|
|
|
|
} // namespace ws::model
|