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

258 lines
8.7 KiB
C++

#pragma once
#include "creeper-qt/utility/painter/common.hh"
#include <qicon.h>
#include <qpainterpath.h>
namespace creeper::painter::internal {
struct EraseRectangle : public CommonProps {
auto operator()(qt::painter& painter) const noexcept {
painter.save();
painter.setCompositionMode(QPainter::CompositionMode_DestinationOut);
painter.setBrush(Qt::black);
painter.setPen(Qt::NoPen);
painter.drawRect(rect());
painter.restore();
}
};
struct Rectangle : public CommonProps, ShapeProps {
auto operator()(qt::painter& painter) const noexcept {
painter.save();
const auto rectangle = qt::rect { origin, size };
painter.setBrush(container_color);
painter.setPen(Qt::NoPen);
painter.drawRect(rectangle);
if (outline_width > 0) {
const auto thickness = outline_width / 2;
const auto inliner = rectangle.adjusted(thickness, thickness, -thickness, -thickness);
painter.setPen({ outline_color, outline_width });
painter.setBrush(Qt::NoBrush);
painter.drawRect(inliner);
}
painter.restore();
}
};
struct RoundedRectangle : public CommonProps, ShapeProps {
double radius_tl = 0;
double radius_tr = 0;
double radius_bl = 0;
double radius_br = 0;
auto set_radiuses(double r) {
radius_tl = r;
radius_tr = r;
radius_bl = r;
radius_br = r;
}
auto operator()(qt::painter& painter) const noexcept {
painter.save();
painter.setRenderHint(QPainter::Antialiasing);
const auto rect = qt::rect { origin, size };
const auto outline_shape =
make_rounded_rect_path(rect, radius_tl, radius_tr, radius_br, radius_bl);
painter.setPen(Qt::NoPen);
painter.setBrush(container_color);
painter.drawPath(outline_shape);
if (outline_width > 0) {
const auto thickness = outline_width;
const auto inliner_f = [=](double r) { return std::max(r - thickness / 2, 0.); };
const auto inliner_rect =
rect.adjusted(thickness / 2, thickness / 2, -thickness / 2, -thickness / 2);
const auto inliner_shape = make_rounded_rect_path(inliner_rect, inliner_f(radius_tl),
inliner_f(radius_tr), inliner_f(radius_br), inliner_f(radius_bl));
painter.setBrush(Qt::NoBrush);
painter.setPen({ outline_color, outline_width });
painter.drawPath(inliner_shape);
}
painter.restore();
}
static constexpr auto make_rounded_rect_path(
const qt::rect& rect, qreal tl, qreal tr, qreal br, qreal bl) noexcept -> QPainterPath {
auto path = QPainterPath {};
const auto max_radius = std::min(rect.width(), rect.height()) / 2.0;
const auto clamp = [&](qreal r) -> qreal {
return r < 0 ? max_radius : std::min(r, max_radius);
};
tl = clamp(tl);
tr = clamp(tr);
br = clamp(br);
bl = clamp(bl);
const auto Arc = [](qreal x, qreal y, qreal r,
int start_angle) -> std::tuple<qt::rect, int, int> {
return { qt::rect(x, y, 2 * r, 2 * r), start_angle, -90 };
};
path.moveTo(rect.topLeft() + qt::point(tl, 0));
path.lineTo(rect.topRight() - qt::point(tr, 0));
const auto [tr_rect, tr_start, tr_span] =
Arc(rect.topRight().x() - 2 * tr, rect.topRight().y(), tr, 90);
path.arcTo(tr_rect, tr_start, tr_span);
path.lineTo(rect.bottomRight() - qt::point(0, br));
const auto [br_rect, br_start, br_span] =
Arc(rect.bottomRight().x() - 2 * br, rect.bottomRight().y() - 2 * br, br, 0);
path.arcTo(br_rect, br_start, br_span);
path.lineTo(rect.bottomLeft() + qt::point(bl, 0));
const auto [bl_rect, bl_start, bl_span] =
Arc(rect.bottomLeft().x(), rect.bottomLeft().y() - 2 * bl, bl, 270);
path.arcTo(bl_rect, bl_start, bl_span);
path.lineTo(rect.topLeft() + qt::point(0, tl));
const auto [tl_rect, tl_start, tl_span] =
Arc(rect.topLeft().x(), rect.topLeft().y(), tl, 180);
path.arcTo(tl_rect, tl_start, tl_span);
path.closeSubpath();
return path;
}
};
struct Text : CommonProps {
qt::string text;
qt::font font;
qt::color color = Qt::black;
qt::text_option text_option;
qt::real scale = 1.;
auto operator()(qt::painter& painter) const noexcept {
painter.save();
painter.scale(scale, scale);
const auto origin_rect = rect();
const auto offset_x = origin_rect.x() * (1.0 - scale);
const auto center_y = origin_rect.y() + origin_rect.height() / 2.0;
const auto offset_y = center_y * (1.0 - scale);
painter.translate(offset_x, offset_y);
painter.setBrush(Qt::NoBrush);
painter.setPen(color);
painter.setFont(font);
const auto scaled_rect = qt::rect {
origin_rect.x() / scale,
origin_rect.y() / scale,
origin_rect.width() / scale,
origin_rect.height() / scale,
};
painter.drawText(scaled_rect, text, text_option);
painter.restore();
}
};
struct Icon : CommonProps {
using IconSource = qt::icon;
using FontSource = std::tuple<qt::string, qt::string>;
using MutiSource = std::variant<FontSource, IconSource>;
MutiSource source;
qt::color color = Qt::black;
auto operator()(qt::painter& painter) const noexcept {
if (size.isEmpty()) return;
painter.save();
const auto rect = qt::rect { origin, size };
std::visit(
[&](auto& value) {
using T = std::decay_t<decltype(value)>;
if constexpr (std::is_same_v<T, FontSource>) {
const auto& [font_family, code] = value;
auto font = qt::font { font_family };
font.setPointSizeF(std::min(size.height(), size.width()));
auto option = qt::text_option {};
option.setAlignment(Qt::AlignCenter);
painter.setFont(font);
painter.setPen(color);
painter.setBrush(Qt::NoBrush);
painter.drawText(rect, code, option);
} else if constexpr (std::is_same_v<T, IconSource>) {
const auto& icon_source = value;
icon_source.paint(&painter, rect.toRect());
}
},
source);
painter.restore();
}
};
}
namespace creeper::painter {
/// Export Rounded Rectangle
using RadiusTL = SetterProp<common::pro::Token, double,
[](auto& self, auto radius) { self.radius_tl = radius; }>;
using RadiusTR = SetterProp<common::pro::Token, double,
[](auto& self, auto radius) { self.radius_tr = radius; }>;
using RadiusBL = SetterProp<common::pro::Token, double,
[](auto& self, auto radius) { self.radius_bl = radius; }>;
using RadiusBR = SetterProp<common::pro::Token, double,
[](auto& self, auto radius) { self.radius_br = radius; }>;
using Radiuses = SetterProp<common::pro::Token, double,
[](auto& self, auto radius) { self.set_radiuses(radius); }>;
/// Export Text
using Text = DerivedProp<common::pro::Token, qt::string,
[](auto& self, const auto& text) { self.text = text; }>;
using Font = DerivedProp<common::pro::Token, qt::font,
[](auto& self, const auto& font) { self.font = font; }>;
using Color = DerivedProp<common::pro::Token, qt::color,
[](auto& self, const auto& color) { self.color = color; }>;
using Scale = SetterProp<common::pro::Token, qt::real,
[](auto& self, const auto& scale) { self.scale = scale; }>;
using TextOption = DerivedProp<common::pro::Token, qt::text_option,
[](auto& self, const auto& option) { self.text_option = option; }>;
/// Export Icon
struct Icon : common::pro::Token {
using T = internal::Icon;
T::MutiSource source;
constexpr explicit Icon(const qt::string& font, const qt::string& code) noexcept
: source { T::FontSource { font, code } } { }
constexpr explicit Icon(const qt::icon& icon) noexcept
: source { T::IconSource { icon } } { }
auto apply(auto& self) const noexcept { self.source = source; }
};
namespace Paint {
using EraseRectangle = Declarative<internal::EraseRectangle, CheckerOr<common::pro::checker>>;
using Rectangle = Declarative<internal::Rectangle, CheckerOr<common::pro::checker>>;
using RoundedRectangle =
Declarative<internal::RoundedRectangle, CheckerOr<common::pro::checker>>;
using Text = Declarative<internal::Text, CheckerOr<common::pro::checker>>;
using Icon = Declarative<internal::Icon, CheckerOr<common::pro::checker>>;
}
}