#include "roundedclip.hpp" #include #include #include #include #include namespace ZShell::components { RoundedClip::RoundedClip(QQuickItem* parent) : QQuickItem(parent) { setFlag(ItemHasContents); setClip(true); } bool RoundedClip::updateRadius(qreal& member, qreal radius) { if (qFuzzyCompare(member + 1.0, radius + 1.0)) return false; member = radius; update(); return true; } void RoundedClip::geometryChange( const QRectF& newGeometry, const QRectF& oldGeometry) { QQuickItem::geometryChange(newGeometry, oldGeometry); if (newGeometry.size() != oldGeometry.size()) update(); } QSGNode* RoundedClip::updatePaintNode( QSGNode* oldNode, UpdatePaintNodeData* data) { Q_UNUSED(oldNode) Q_UNUSED(data) QQuickItemPrivate* priv = QQuickItemPrivate::get(this); QQuickDefaultClipNode* clip = priv->clipNode(); if (!clip) return nullptr; // Clip node geometry is in item-local coordinates. The clip node is a // child of the item's transform node, so the clip rect is // QRectF(0, 0, width, height), i.e. boundingRect()/clipRect(). const QRectF r(0, 0, width(), height()); // Per-corner radii fall back to the shared `radius` shorthand. const qreal tl = m_topLeftRadius > 0 ? m_topLeftRadius : m_radius; const qreal tr = m_topRightRadius > 0 ? m_topRightRadius : m_radius; const qreal br = m_bottomRightRadius > 0 ? m_bottomRightRadius : m_radius; const qreal bl = m_bottomLeftRadius > 0 ? m_bottomLeftRadius : m_radius; // Nothing rounded: plain rectangular clip. if (tl <= 0 && tr <= 0 && br <= 0 && bl <= 0) { clip->setRect(r); clip->update(); return nullptr; } // All corners equal: let QQuickDefaultClipNode build its own known-correct // scanline geometry. This is the working path. if (qFuzzyCompare(tl, tr) && qFuzzyCompare(tr, br) && qFuzzyCompare(br, bl)) { clip->setRect(r); clip->setRadius(tl); clip->update(); return nullptr; } // Per-corner radii. setRadius() supports only a single value, so we // must supply the geometry ourselves. Build it as a triangle fan from an // interior point so the polygon interior fills for the stencil clip. const qreal half = std::min(r.width() / 2, r.height() / 2); const qreal rtl = std::min(tl, half); const qreal rtr = std::min(tr, half); const qreal rbr = std::min(br, half); const qreal rbl = std::min(bl, half); auto segments = [](qreal radius) { return qMin(30, int(std::ceil(radius))); }; const int s_tl = segments(rtl); const int s_tr = segments(rtr); const int s_br = segments(rbr); const int s_bl = segments(rbl); clip->setRect(r); clip->setIsRectangular(false); QSGGeometry* g = clip->geometry(); g->setDrawingMode(QSGGeometry::DrawTriangleFan); // One apex + one point per arc step + one point per zero-radius corner, // plus a closing apex to complete the fan. g->allocate(1 + s_tl + s_tr + s_br + s_bl + 5); QSGGeometry::Point2D* vertices = g->vertexDataAsPoint2D(); int vi = 0; // Apex at an interior point. vertices[vi++] = QSGGeometry::Point2D(float(r.center().x()), float(r.center().y())); // Each corner contributes its arc points (i = 0..segments) from its // edge-entry tangent to its edge-exit tangent. Consecutive corners are // joined by the implicit straight edges, forming the rounded rectangle. auto addArc = [&](int segs, qreal cx, qreal cy, qreal rad, qreal a0, qreal a1) { for (int i = 0; i <= segs; ++i) { const qreal angle = a0 + (a1 - a0) * qreal(i) / qreal(segs); vertices[vi++] = QSGGeometry::Point2D( float(cx + rad * std::cos(angle)), float(cy + rad * std::sin(angle))); } }; addArc(s_tl, rtl, rtl, rtl, M_PI, 3.0 * M_PI / 2.0); addArc(s_tr, r.width() - rtr, rtr, rtr, -M_PI / 2.0, 0.0); addArc(s_br, r.width() - rbr, r.height() - rbr, rbr, 0.0, M_PI / 2.0); addArc(s_bl, rbl, r.height() - rbl, rbl, M_PI / 2.0, M_PI); // Close the fan back to the first perimeter vertex. vertices[vi] = vertices[1]; clip->markDirty(QSGNode::DirtyGeometry); return nullptr; } } // namespace ZShell::components