feat: initial commit
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
#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
|
||||
Reference in New Issue
Block a user