#pragma once #include "creeper-qt/utility/painter/common.hh" #include #include 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 { 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; using MutiSource = std::variant; 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; if constexpr (std::is_same_v) { 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) { const auto& icon_source = value; icon_source.paint(&painter, rect.toRect()); } }, source); painter.restore(); } }; } namespace creeper::painter { /// Export Rounded Rectangle using RadiusTL = SetterProp; using RadiusTR = SetterProp; using RadiusBL = SetterProp; using RadiusBR = SetterProp; using Radiuses = SetterProp; /// Export Text using Text = DerivedProp; using Font = DerivedProp; using Color = DerivedProp; using Scale = SetterProp; using TextOption = DerivedProp; /// 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>; using Rectangle = Declarative>; using RoundedRectangle = Declarative>; using Text = Declarative>; using Icon = Declarative>; } }