Files
ts-qt/creeper-qt/utility/painter/helper.hh
2025-11-25 15:59:47 +08:00

225 lines
7.4 KiB
C++

#pragma once
#include <qpainter.h>
#include <qpainterpath.h>
namespace creeper::util {
auto print_paint_event_count() noexcept -> void;
/// @brief 隐藏冗杂的细节,解放命令式的绘图调用
class PainterHelper {
public:
using Renderer = std::function<void(QPainter&)>;
explicit PainterHelper(QPainter& painter)
: painter(painter) {
print_paint_event_count();
}
inline void done() { }
inline PainterHelper& apply(const Renderer& renderer) {
renderer(painter);
return *this;
}
inline PainterHelper& apply(const std::ranges::range auto& renderers)
requires std::same_as<std::ranges::range_value_t<decltype(renderers)>, Renderer>
{
for (const auto& renderer : renderers)
renderer(painter);
return *this;
}
public:
inline PainterHelper& set_brush(const QBrush& brush) {
painter.setBrush(brush);
return *this;
}
inline PainterHelper& set_pen(const QPen& pen) {
painter.setPen(pen);
return *this;
}
inline PainterHelper& set_opacity(double opacity) {
painter.setOpacity(opacity);
return *this;
}
inline PainterHelper& set_render_hint(QPainter::RenderHint hint, bool on = true) {
painter.setRenderHint(hint, on);
return *this;
}
inline PainterHelper& set_render_hints(QPainter::RenderHints hint, bool on = true) {
painter.setRenderHints(hint, on);
return *this;
}
inline PainterHelper& set_clip_path(
const QPainterPath& path, Qt::ClipOperation operation = Qt::ReplaceClip) {
painter.setClipPath(path, operation);
return *this;
}
public:
inline PainterHelper& point(const QColor& color, double radius, const QPointF& point) {
pen_only({ color, radius * 2 }).drawPoint(point);
return *this;
}
inline PainterHelper& ellipse(const QColor& background, const QColor& border_color,
double border_width, const QRectF& rect) {
brush_only({ background }).drawEllipse(rect);
const auto half = border_width / 2;
if (border_width != 0)
pen_only({ border_color, border_width })
.drawEllipse(rect.adjusted(half, half, -half, -half));
return *this;
}
inline PainterHelper& ellipse(const QColor& background, const QColor& border_color,
double border_width, const QPointF& origin, double radius_x, double radius_y) {
brush_only({ background }).drawEllipse(origin, radius_x, radius_y);
if (border_width != 0)
pen_only({ border_color, border_width })
.drawEllipse(origin, radius_x - border_width / 2, radius_y - border_width / 2);
return *this;
}
inline PainterHelper& rectangle(const QColor& background, const QColor& border_color,
double border_width, const QRectF& rect) {
brush_only({ background }).drawRect(rect);
if (border_width == 0) return *this;
const auto inliner_border_rectangle =
rect.adjusted(border_width / 2, border_width / 2, -border_width / 2, -border_width / 2);
pen_only({ border_color, border_width }).drawRect(inliner_border_rectangle);
return *this;
}
inline PainterHelper& rounded_rectangle(const QColor& background, const QColor& border_color,
double border_width, const QRectF& rect, double radius_x, double radius_y) {
brush_only({ background }).drawRoundedRect(rect, radius_x, radius_y);
if (border_width == 0) return *this;
const auto inliner_border_rectangle =
rect.adjusted(border_width / 2, border_width / 2, -border_width / 2, -border_width / 2);
pen_only({ border_color, border_width })
.drawRoundedRect(inliner_border_rectangle, std::max(radius_x - border_width / 2, 0.),
std::max(radius_y - border_width / 2, 0.));
return *this;
}
inline PainterHelper& rounded_rectangle(const QColor& background, const QColor& border_color,
double border_width, const QRectF& rect, double tl, double tr, double br, double bl) {
const auto path = make_rounded_rect_path(rect, tl, tr, br, bl);
brush_only({ background }).drawPath(path);
if (border_width == 0) return *this;
const auto inliner = [=](double r) { return std::max(r - border_width / 2, 0.); };
const auto inliner_border_rectangle =
rect.adjusted(border_width / 2, border_width / 2, -border_width / 2, -border_width / 2);
const auto inliner_path = make_rounded_rect_path(
inliner_border_rectangle, inliner(tl), inliner(tr), inliner(br), inliner(bl));
pen_only({ border_color, border_width }).drawPath(inliner_path);
return *this;
}
// Pen 是以路径为中心来绘制图片,有绘出 rect 导致画面被裁切的可能,由于是 path 类型,不好做限制
inline PainterHelper& path(const QColor& background, const QColor& border_color,
double border_width, const QPainterPath& path) {
brush_only({ background }).drawPath(path);
if (border_width != 0) pen_only({ border_color, border_width }).drawPath(path);
return *this;
}
inline PainterHelper& pixmap(const QPixmap& pixmap, const QRectF& dst, const QRectF& src) {
painter.drawPixmap(dst, pixmap, src);
return *this;
}
inline PainterHelper& simple_text(const QString& text, const QFont& font, const QColor& color,
const QRectF& rect, Qt::Alignment alignment) {
painter.setRenderHint(QPainter::TextAntialiasing);
painter.setFont(font);
painter.setBrush(Qt::NoBrush);
painter.setPen({ color });
painter.drawText(rect, alignment, text);
return *this;
}
private:
QPainter& painter;
QPainter& pen_only(const QPen& pen) {
painter.setBrush(Qt::NoBrush);
painter.setPen(pen);
return painter;
}
QPainter& brush_only(const QBrush& brush) {
painter.setBrush(brush);
painter.setPen(Qt::NoPen);
return painter;
}
static auto make_rounded_rect_path(
const QRectF& rect, qreal tl, qreal tr, qreal br, qreal bl) noexcept -> QPainterPath {
auto path = QPainterPath {};
const auto half_width = rect.width() / 2.0;
const auto half_height = rect.height() / 2.0;
const auto max_radius = std::min(half_width, half_height);
const auto clamp_radius = [&](qreal r) {
return r < 0 ? max_radius : std::min(r, max_radius);
};
tl = clamp_radius(tl);
tr = clamp_radius(tr);
br = clamp_radius(br);
bl = clamp_radius(bl);
path.moveTo(rect.topLeft() + QPointF(tl, 0));
path.lineTo(rect.topRight() - QPointF(tr, 0));
path.arcTo(
QRectF(rect.topRight().x() - 2 * tr, rect.topRight().y(), 2 * tr, 2 * tr), 90, -90);
path.lineTo(rect.bottomRight() - QPointF(0, br));
path.arcTo(QRectF(rect.bottomRight().x() - 2 * br, rect.bottomRight().y() - 2 * br, 2 * br,
2 * br),
0, -90);
path.lineTo(rect.bottomLeft() + QPointF(bl, 0));
path.arcTo(QRectF(rect.bottomLeft().x(), rect.bottomLeft().y() - 2 * bl, 2 * bl, 2 * bl),
270, -90);
path.lineTo(rect.topLeft() + QPointF(0, tl));
path.arcTo(QRectF(rect.topLeft().x(), rect.topLeft().y(), 2 * tl, 2 * tl), 180, -90);
path.closeSubpath();
return path;
}
};
}