#pragma once #include "modern-qt/utility/solution/round-angle.hh" #include "modern-qt/utility/wrapper/common.hh" #include "modern-qt/utility/wrapper/property.hh" #include "modern-qt/widget/shape/shape.hh" #include "modern-qt/widget/widget.hh" #include #include #include namespace creeper::wave_circle::internal { class WaveCircle : public Shape { public: auto set_flange_number(uint8_t number) noexcept { generate_request_ = true; flange_number_ = number; } auto set_flange_radius(double radius) noexcept { generate_request_ = true; flange_radius_ = radius; } auto set_overall_radius(double radius) noexcept { generate_request_ = true; overall_radius_ = radius; } auto set_protruding_ratio(double ratio) noexcept { generate_request_ = true; protruding_ratio_ = ratio; } protected: auto paintEvent(QPaintEvent*) -> void override { if (generate_request_) generate_path(); auto painter = QPainter { this }; painter.setRenderHint(QPainter::Antialiasing, true); painter.setOpacity(1); painter.setBrush({ background_ }); painter.setPen(QPen { border_color_, border_width_, Qt::SolidLine, Qt::RoundCap, }); painter.drawPath(path_cache_); } auto resizeEvent(QResizeEvent* e) -> void override { Shape::resizeEvent(e); generate_request_ = true; } private: bool generate_request_ = true; QPainterPath path_cache_; int8_t flange_number_ = 12; double flange_radius_ = 10; double overall_radius_ = 100; double protruding_ratio_ = 0.8; auto generate_path() noexcept -> void { const auto center = QPointF(width() / 2., height() / 2.); const auto step = 2 * std::numbers::pi / flange_number_; const auto radius = std::min(overall_radius_, std::min(width(), height())); std::vector outside(flange_number_ + 2), inside(flange_number_ + 2); for (auto&& [index, point] : std::views::enumerate(std::views::zip(outside, inside))) { auto& [outside, inside] = point; outside.setX(radius * std::cos(-index * step)); outside.setY(radius * std::sin(-index * step)); inside.setX(protruding_ratio_ * radius * std::cos(double(-index + 0.5) * step)); inside.setY(protruding_ratio_ * radius * std::sin(double(-index + 0.5) * step)); } auto begin = QPointF {}; path_cache_ = QPainterPath {}; for (int index = 0; index < flange_number_; index++) { const auto convex = RoundAngleSolution(center + outside[index], center + inside[index], center + inside[index + 1], flange_radius_); const auto concave = RoundAngleSolution(center + inside[index + 1], center + outside[index + 1], center + outside[index], flange_radius_); if (index == 0) begin = convex.start, path_cache_.moveTo(begin); path_cache_.lineTo(convex.start); path_cache_.arcTo(convex.rect, convex.angle_begin, convex.angle_length); path_cache_.lineTo(concave.end); path_cache_.arcTo( concave.rect, concave.angle_begin + concave.angle_length, -concave.angle_length); } path_cache_.lineTo(begin); } }; } namespace creeper::wave_circle::pro { using Token = common::Token; using Background = common::pro::Background; using BorderWidth = common::pro::BorderWidth; using BorderColor = common::pro::BorderColor; using FlangeNumber = SetterProp; using FlangeRadius = SetterProp; using OverallRadius = SetterProp; using ProtrudingRatio = SetterProp; template concept trait = std::derived_from; CREEPER_DEFINE_CHECKER(trait); using namespace widget::pro; } namespace creeper { using WaveCircle = Declarative>; }