File indexing completed on 2025-02-02 04:26:10
0001 /* SPDX-FileCopyrightText: 2022 Marco Martin <mart@kde.org> 0002 * SPDX-FileCopyrightText: 2024 Noah Davis <noahadvs@gmail.com> 0003 * SPDX-License-Identifier: LGPL-2.0-or-later 0004 */ 0005 0006 #include "Traits.h" 0007 #include "Geometry.h" 0008 #include <QLocale> 0009 #include <QUuid> 0010 0011 using namespace Qt::StringLiterals; 0012 0013 // Stroke 0014 0015 QPen Traits::Stroke::defaultPen() 0016 { 0017 return {Qt::NoBrush, 1.0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin}; 0018 } 0019 0020 int Traits::Text::textFlags() const 0021 { 0022 return (index() == Text::String ? Qt::AlignLeft | Qt::AlignTop : Qt::AlignCenter) // 0023 | Qt::TextDontClip | Qt::TextExpandTabs | Qt::TextIncludeTrailingSpaces; 0024 } 0025 0026 QString Traits::Text::text() const 0027 { 0028 if (index() == String) { 0029 return std::get<String>(*this); 0030 } else if (index() == Number) { 0031 return QLocale::system().toString(std::get<Number>(*this)); 0032 } 0033 return {}; 0034 } 0035 0036 // ImageEffects 0037 0038 static const auto factorKey = u"factor"_s; 0039 QImage imageCopyHelper(const QImage &image, const QRectF ©Rect) 0040 { 0041 if (copyRect.size() != image.size()) { 0042 return image.copy(std::floor<int>(copyRect.x()), 0043 std::floor<int>(copyRect.y()), // 0044 std::ceil<int>(copyRect.width()), 0045 std::ceil<int>(copyRect.height())); 0046 } 0047 return image; 0048 } 0049 0050 Traits::ImageEffects::Blur::Blur(uint factor) 0051 : factor(factor) 0052 { 0053 } 0054 0055 bool Traits::ImageEffects::Blur::isValid() const 0056 { 0057 return factor > 1; 0058 } 0059 0060 QImage Traits::ImageEffects::Blur::image(std::function<QImage()> getImage, QRectF rect, qreal dpr) const 0061 { 0062 if (!isValid()) { 0063 return {}; 0064 } 0065 if ((backingStoreCache.isNull() // 0066 || backingStoreCache.devicePixelRatio() != dpr // 0067 || backingStoreCache.text(factorKey).toFloat() != factor) 0068 && getImage) { 0069 backingStoreCache = getImage(); 0070 // Scale the factor with the devicePixelRatio. 0071 // This way high DPI pictures aren't visually affected less than standard DPI pictures. 0072 const auto effectFactor = factor * dpr; 0073 auto scaleDown = QTransform::fromScale(1 / effectFactor, 1 / effectFactor); 0074 auto scaleUp = QTransform::fromScale(effectFactor, effectFactor); 0075 // A poor man's blur. It's fast, but not high quality. 0076 // It's somewhat blocky, but it's definitely blurry. 0077 backingStoreCache = backingStoreCache.transformed(scaleDown, Qt::SmoothTransformation); 0078 backingStoreCache = backingStoreCache.transformed(scaleUp, Qt::SmoothTransformation); 0079 backingStoreCache.setDevicePixelRatio(dpr); 0080 backingStoreCache.setText(factorKey, QString::number(factor)); 0081 } 0082 rect = ::Geometry::rectScaled(rect, backingStoreCache.devicePixelRatio()); 0083 return imageCopyHelper(backingStoreCache, rect); 0084 } 0085 0086 Traits::ImageEffects::Pixelate::Pixelate(uint factor) 0087 : factor(factor) 0088 { 0089 } 0090 0091 bool Traits::ImageEffects::Pixelate::isValid() const 0092 { 0093 return factor > 1; 0094 } 0095 0096 QImage Traits::ImageEffects::Pixelate::image(std::function<QImage()> getImage, QRectF rect, qreal dpr) const 0097 { 0098 if (!isValid()) { 0099 return {}; 0100 } 0101 if ((backingStoreCache.isNull() // 0102 || backingStoreCache.devicePixelRatio() != dpr // 0103 || backingStoreCache.text(factorKey).toFloat() != factor) 0104 && getImage) { 0105 backingStoreCache = getImage(); 0106 // Scale the factor with the devicePixelRatio. 0107 // This way high DPI pictures aren't visually affected less than standard DPI pictures. 0108 const auto effectFactor = factor * dpr; 0109 auto scaleDown = QTransform::fromScale(1 / effectFactor, 1 / effectFactor); 0110 auto scaleUp = QTransform::fromScale(effectFactor, effectFactor); 0111 // Smooth when scaling down to average out the colors. 0112 backingStoreCache = backingStoreCache.transformed(scaleDown, Qt::SmoothTransformation); 0113 backingStoreCache = backingStoreCache.transformed(scaleUp, Qt::FastTransformation); 0114 backingStoreCache.setDevicePixelRatio(dpr); 0115 backingStoreCache.setText(factorKey, QString::number(factor)); 0116 } 0117 rect = ::Geometry::rectScaled(rect, backingStoreCache.devicePixelRatio()); 0118 return imageCopyHelper(backingStoreCache, rect); 0119 } 0120 0121 // Functions 0122 0123 Traits::Translation Traits::unTranslateScale(qreal sx, qreal sy, const QPointF &oldPoint) 0124 { 0125 return {-oldPoint.x() * sx + oldPoint.x(), -oldPoint.y() * sy + oldPoint.y()}; 0126 } 0127 0128 Traits::Scale Traits::scaleForSize(const QSizeF &oldSize, const QSizeF &newSize) 0129 { 0130 // We should never divide by zero and we don't need fractional sizes less than 1. 0131 auto absWidth = std::abs(oldSize.width()); 0132 auto absHeight = std::abs(oldSize.height()); 0133 auto wSign = std::copysign(1.0, oldSize.width()); 0134 auto hSign = std::copysign(1.0, oldSize.height()); 0135 // Don't allow an absolute size less than 1x1. 0136 const auto wDivisor = std::max(1.0, absWidth) * wSign; 0137 const auto hDivisor = std::max(1.0, absHeight) * hSign; 0138 return {newSize.width() / wDivisor, newSize.height() / hDivisor}; 0139 } 0140 0141 QPainterPath Traits::minPath(const QPainterPath &path) 0142 { 0143 if (path.isEmpty()) { 0144 auto start = path.elementCount() > 0 ? path.elementAt(0) : QPainterPath::Element{}; 0145 QPainterPath dotPath(start); 0146 dotPath.lineTo(start.x + 0.0001, start.y); 0147 return dotPath; 0148 } 0149 return path; 0150 } 0151 0152 QPainterPath Traits::arrowHead(const QLineF &mainLine, qreal strokeWidth) 0153 { 0154 const auto &end = mainLine.p2(); 0155 // This should leave a decently sized gap between the arrow head and shaft 0156 // and a decently sized length for all stroke widths. 0157 // Arrow head length will grow with stroke width. 0158 const qreal length = qMax(8.0, strokeWidth * 3.0); 0159 const qreal angle = mainLine.angle() + 180; 0160 auto headLine1 = QLineF::fromPolar(length, angle + 30).translated(end); 0161 auto headLine2 = QLineF::fromPolar(length, angle - 30).translated(end); 0162 QPainterPath path(headLine1.p2()); 0163 path.lineTo(end); 0164 path.lineTo(headLine2.p2()); 0165 return path; 0166 } 0167 0168 QPainterPath Traits::createTextPath(const OptTuple &traits) 0169 { 0170 auto &geometry = std::get<Geometry::Opt>(traits); 0171 auto &text = std::get<Text::Opt>(traits); 0172 if (!geometry) { 0173 return {}; 0174 } 0175 if (!text) { 0176 return geometry->path; 0177 } 0178 const auto &start = geometry->path.elementCount() > 0 ? geometry->path.elementAt(0) : QPainterPath::Element{}; 0179 QRectF rect{start, start}; 0180 QFontMetricsF fm(text->font); 0181 QPainterPath path{start}; 0182 if (text->index() == Text::String) { 0183 // Same as QPainter's default 0184 const auto tabStopDistance = qRound(fm.horizontalAdvance(u'x') * 8); 0185 auto size = fm.size(text->textFlags(), text->text(), tabStopDistance); 0186 size.rwidth() = std::max(size.width(), fm.height()); 0187 size.rheight() = std::max(size.height(), fm.height()); 0188 // TODO: RTL language reversal 0189 rect.adjust(0, -fm.height() / 2, size.width(), size.height() - fm.height() / 2); 0190 path.addRect(rect); 0191 } else if (text->index() == Text::Number) { 0192 auto margin = fm.capHeight() * 1.33; 0193 rect.adjust(-margin, -margin, margin, margin); 0194 path.addEllipse(rect); 0195 } 0196 return path; 0197 } 0198 0199 QPainterPath Traits::createStrokePath(const OptTuple &traits) 0200 { 0201 auto &geometry = std::get<Geometry::Opt>(traits); 0202 auto &stroke = std::get<Stroke::Opt>(traits); 0203 if (!geometry && !stroke) { 0204 return {}; 0205 } 0206 QPainterPathStroker stroker(stroke->pen); 0207 auto minPath = Traits::minPath(geometry->path); // Will always have at least 2 points. 0208 if (auto &arrow = std::get<Arrow::Opt>(traits)) { 0209 const int size = minPath.elementCount(); 0210 const QLineF lastLine{minPath.elementAt(size - 2), minPath.elementAt(size - 1)}; 0211 auto arrowHead = Traits::arrowHead(lastLine, stroke->pen.widthF()); 0212 return stroker.createStroke(minPath) | stroker.createStroke(arrowHead); 0213 } else { 0214 return stroker.createStroke(minPath); 0215 } 0216 } 0217 0218 QPainterPath Traits::createMousePath(const OptTuple &traits) 0219 { 0220 auto &geometry = std::get<Geometry::Opt>(traits); 0221 auto &stroke = std::get<Stroke::Opt>(traits); 0222 QPainterPath mousePath; 0223 if (geometry && !geometry->path.isEmpty()) { 0224 mousePath = geometry->path; 0225 } 0226 // Ensure you can click anywhere within the bounds. 0227 mousePath.setFillRule(Qt::WindingFill); 0228 if (stroke && !stroke->path.isEmpty()) { 0229 mousePath |= stroke->path; 0230 } 0231 0232 return mousePath.simplified(); 0233 } 0234 0235 QRectF Traits::createVisualRect(const OptTuple &traits) 0236 { 0237 auto &geometry = std::get<Geometry::Opt>(traits); 0238 auto &stroke = std::get<Stroke::Opt>(traits); 0239 if (!geometry) { 0240 return {}; 0241 } 0242 QRectF visualRect; 0243 if (stroke) { 0244 visualRect = stroke->path.boundingRect() | geometry->path.boundingRect(); 0245 } else { 0246 visualRect = geometry->path.boundingRect(); 0247 } 0248 // Add Shadow margins if not empty. 0249 auto &shadow = std::get<Shadow::Opt>(traits); 0250 if (shadow && shadow->enabled && !visualRect.isEmpty()) { 0251 visualRect += Shadow::margins; 0252 } 0253 return visualRect; 0254 } 0255 0256 void Traits::fastInitOptTuple(OptTuple &traits) 0257 { 0258 auto &geometry = std::get<Geometry::Opt>(traits); 0259 if (geometry) { 0260 // Set Geometry::path from Font and Text/Number if empty. 0261 auto &text = std::get<Text::Opt>(traits); 0262 if (geometry->path.isEmpty() && text) { 0263 geometry->path = Traits::createTextPath(traits); 0264 } 0265 // Set Stroke::path from Geometry and Arrow if empty. 0266 auto &stroke = std::get<Stroke::Opt>(traits); 0267 if (stroke && stroke->path.isEmpty()) { 0268 stroke->path = createStrokePath(traits); 0269 } 0270 // Set Geometry::visualRect from Stroke and Geometry if empty. 0271 if (geometry->visualRect.isEmpty()) { 0272 geometry->visualRect = createVisualRect(traits); 0273 } 0274 } 0275 } 0276 0277 void Traits::initOptTuple(OptTuple &traits) 0278 { 0279 fastInitOptTuple(traits); 0280 auto &geometry = std::get<Geometry::Opt>(traits); 0281 if (geometry) { 0282 // Set Geometry::mousePath from Stroke and Geometry if empty. 0283 if (geometry->mousePath.isEmpty()) { 0284 geometry->mousePath = createMousePath(traits); 0285 } 0286 } 0287 } 0288 0289 template<typename T> 0290 void clearForInitHelper(Traits::OptTuple &traits) 0291 { 0292 auto &traitOpt = std::get<std::optional<T>>(traits); 0293 if (!traitOpt) { 0294 return; 0295 } 0296 auto &trait = traitOpt.value(); 0297 if constexpr (std::same_as<T, Traits::Geometry>) { 0298 trait.mousePath.clear(); 0299 trait.visualRect = {}; 0300 } else if constexpr (std::same_as<T, Traits::Stroke>) { 0301 trait.path.clear(); 0302 } else if constexpr (std::same_as<T, Traits::Text>) { 0303 auto &geometry = std::get<Traits::Geometry::Opt>(traits); 0304 if (!geometry) { 0305 return; 0306 } 0307 if (trait.index() == Traits::Text::String) { 0308 QFontMetricsF fm(trait.font); 0309 // TODO: RTL language reversal 0310 QPointF topLeft; 0311 if (geometry->path.elementCount() == 1) { 0312 topLeft = geometry->path.elementAt(0); 0313 } else { 0314 topLeft = geometry->path.boundingRect().topLeft(); 0315 } 0316 geometry->path = QPainterPath{topLeft + QPointF{0, fm.height() / 2}}; 0317 } else if (trait.index() == Traits::Text::Number) { 0318 QPointF point; 0319 if (geometry->path.elementCount() == 1) { 0320 point = geometry->path.elementAt(0); 0321 } else { 0322 point = geometry->path.boundingRect().center(); 0323 } 0324 geometry->path = QPainterPath{point}; 0325 } 0326 } 0327 } 0328 0329 void Traits::clearForInit(OptTuple &traits) 0330 { 0331 clearForInitHelper<Geometry>(traits); 0332 clearForInitHelper<Stroke>(traits); 0333 clearForInitHelper<Text>(traits); 0334 } 0335 0336 void Traits::reInitTraits(OptTuple &traits) 0337 { 0338 clearForInit(traits); 0339 initOptTuple(traits); 0340 } 0341 0342 void Traits::transformTraits(const QTransform &transform, OptTuple &traits) 0343 { 0344 if (transform.isIdentity()) { 0345 return; 0346 } 0347 auto &geometry = std::get<Geometry::Opt>(traits); 0348 auto &text = std::get<Text::Opt>(traits); 0349 bool onlyTranslating = transform.type() == QTransform::TxTranslate || text; 0350 if (geometry && onlyTranslating) { 0351 geometry->path.translate(transform.dx(), transform.dy()); 0352 geometry->mousePath.translate(transform.dx(), transform.dy()); 0353 // This is dependent on other traits, but as long as all traits have, 0354 // the same transformations, transforming at this time should be fine. 0355 geometry->visualRect.translate(transform.dx(), transform.dy()); 0356 } else if (geometry) { 0357 geometry->path = transform.map(geometry->path); 0358 geometry->mousePath = transform.map(geometry->mousePath); 0359 // This is dependent on other traits, but as long as all traits have, 0360 // the same transformations, transforming at this time should be fine. 0361 geometry->visualRect = transform.mapRect(geometry->visualRect); 0362 } 0363 auto &stroke = std::get<Stroke::Opt>(traits); 0364 if (stroke && onlyTranslating) { 0365 // If the stroke already has the arrow in it, 0366 // we shouldn't need to completely regenerate the stroke with QPainterPathStroker. 0367 stroke->path.translate(transform.dx(), transform.dy()); 0368 } else if (stroke) { 0369 stroke->path = transform.map(stroke->path); 0370 } 0371 } 0372 0373 // Whether the values of the traits without std::optional are considered valid. 0374 template<> 0375 bool Traits::isValidTrait<Traits::Geometry>(const Traits::Geometry &trait) 0376 { 0377 return !trait.visualRect.isEmpty() && !trait.path.isEmpty(); 0378 } 0379 template<> 0380 bool Traits::isValidTrait<Traits::Stroke>(const Traits::Stroke &trait) 0381 { 0382 return !trait.path.isEmpty() && trait.pen.style() != Qt::NoPen; 0383 } 0384 template<> 0385 bool Traits::isValidTrait<Traits::Fill>(const Traits::Fill &trait) 0386 { 0387 switch (trait.index()) { 0388 case Fill::Brush: 0389 return std::get<Fill::Brush>(trait) != Qt::NoBrush; 0390 case Fill::Blur: 0391 return std::get<Fill::Blur>(trait).isValid(); 0392 case Fill::Pixelate: 0393 return std::get<Fill::Pixelate>(trait).isValid(); 0394 default: 0395 return false; 0396 } 0397 } 0398 template<> 0399 bool Traits::isValidTrait<Traits::Highlight>(const Traits::Highlight &) 0400 { 0401 return true; 0402 } 0403 template<> 0404 bool Traits::isValidTrait<Traits::Arrow>(const Traits::Arrow &) 0405 { 0406 return true; 0407 } 0408 template<> 0409 bool Traits::isValidTrait<Traits::Text>(const Traits::Text &trait) 0410 { 0411 return trait.brush != Qt::NoBrush // 0412 && (trait.index() == Traits::Text::Number || !trait.text().isEmpty()); 0413 } 0414 template<> 0415 bool Traits::isValidTrait<Traits::Shadow>(const Traits::Shadow &) 0416 { 0417 return true; 0418 } 0419 0420 // Whether the std::optionals are considered valid. 0421 template<typename T> 0422 bool Traits::isValidTraitOpt(const Traits::OptTuple &traits, bool isNullValid) 0423 { 0424 auto &traitOpt = std::get<std::optional<T>>(traits); 0425 if (!traitOpt) { 0426 return isNullValid; 0427 } 0428 auto &trait = traitOpt.value(); 0429 0430 if constexpr (std::same_as<T, Traits::Geometry>) { 0431 return Traits::isValidTrait(trait); 0432 } 0433 0434 // Traits that depend on geometry 0435 auto &geometry = std::get<Traits::Geometry::Opt>(traits); 0436 const bool validGeometry = geometry && Traits::isValidTrait(geometry.value()); 0437 if constexpr (std::same_as<T, Stroke>) { 0438 return validGeometry && Traits::isValidTrait(trait); 0439 } 0440 if constexpr (std::same_as<T, Fill>) { 0441 return validGeometry && Traits::isValidTrait(trait); 0442 } 0443 if constexpr (std::same_as<T, Text>) { 0444 return validGeometry && Traits::isValidTrait(trait); 0445 } 0446 0447 // Traits that depend on vector graphic traits 0448 auto &stroke = std::get<Stroke::Opt>(traits); 0449 auto &fill = std::get<Fill::Opt>(traits); 0450 auto &text = std::get<Text::Opt>(traits); 0451 const bool validStroke = stroke && Traits::isValidTrait(stroke.value()); 0452 const bool validFill = fill && Traits::isValidTrait(fill.value()); 0453 const bool validText = text && Traits::isValidTrait(text.value()); 0454 if constexpr (std::same_as<T, Highlight>) { 0455 return validGeometry && (validStroke || validFill || validText) // 0456 && Traits::isValidTrait(trait); 0457 } 0458 if constexpr (std::same_as<T, Arrow>) { 0459 return validGeometry && (validStroke || validFill || validText) // 0460 && Traits::isValidTrait(trait); 0461 } 0462 if constexpr (std::same_as<T, Shadow>) { 0463 return validGeometry && (validStroke || validFill || validText) // 0464 && Traits::isValidTrait(trait); 0465 } 0466 return false; 0467 } 0468 0469 template<typename... Ts> 0470 bool isValidHelper(const Traits::OptTuple &traits) 0471 { 0472 return (Traits::isValidTraitOpt<Ts>(traits, true) && ...); 0473 } 0474 0475 bool Traits::isValid(const OptTuple &traits) 0476 { 0477 return isValidHelper<Geometry, Stroke, Fill, Highlight, Arrow, Text, Shadow>(traits); 0478 } 0479 0480 bool Traits::isVisible(const OptTuple &traits) 0481 { 0482 return Traits::isValidTraitOpt<Geometry>(traits, false) // 0483 && (Traits::isValidTraitOpt<Stroke>(traits, false) // 0484 || Traits::isValidTraitOpt<Fill>(traits, false) // 0485 || Traits::isValidTraitOpt<Text>(traits, false)); 0486 } 0487 0488 QPainterPath Traits::mousePath(const OptTuple &traits) 0489 { 0490 auto &geometry = std::get<Geometry::Opt>(traits); 0491 return geometry ? geometry->mousePath : QPainterPath{}; 0492 } 0493 0494 QRectF Traits::visualRect(const OptTuple &traits) 0495 { 0496 auto &geometry = std::get<Geometry::Opt>(traits); 0497 return geometry ? geometry->visualRect : QRectF{}; 0498 } 0499 0500 // QDebug operator<< declarations 0501 0502 // Traits 0503 0504 QDebug operator<<(QDebug debug, const Traits::Geometry &trait) 0505 { 0506 using namespace Traits; 0507 QDebugStateSaver stateSaver(debug); 0508 debug.nospace(); 0509 debug << "Geometry" << '('; 0510 debug << (const void *)&trait; 0511 debug << ",\n path=" << trait.path; 0512 debug << ",\n mousePath=" << trait.mousePath; 0513 debug << ",\n visualRect=" << trait.visualRect; 0514 debug << ')'; 0515 return debug; 0516 } 0517 0518 QDebug operator<<(QDebug debug, const Traits::Stroke &trait) 0519 { 0520 using namespace Traits; 0521 QDebugStateSaver stateSaver(debug); 0522 debug.nospace(); 0523 debug << "Stroke" << '('; 0524 debug << (const void *)&trait; 0525 debug << ",\n pen=" << trait.pen; 0526 debug << ",\n path=" << trait.path; 0527 debug << ')'; 0528 return debug; 0529 } 0530 0531 QDebug operator<<(QDebug debug, const Traits::Fill &trait) 0532 { 0533 using namespace Traits; 0534 QDebugStateSaver stateSaver(debug); 0535 debug.nospace(); 0536 debug << "Fill" << '('; 0537 debug << (const void *)&trait; 0538 debug << ", "; 0539 switch (trait.index()) { 0540 case Fill::Brush: 0541 debug << std::get<Fill::Brush>(trait); 0542 break; 0543 case Fill::Blur: 0544 debug << std::get<Fill::Blur>(trait); 0545 break; 0546 case Fill::Pixelate: 0547 debug << std::get<Fill::Pixelate>(trait); 0548 break; 0549 default: 0550 break; 0551 } 0552 debug << ')'; 0553 return debug; 0554 } 0555 0556 QDebug operator<<(QDebug debug, const Traits::Highlight &trait) 0557 { 0558 using namespace Traits; 0559 QDebugStateSaver stateSaver(debug); 0560 debug.nospace(); 0561 debug << "Highlight" << '('; 0562 debug << (const void *)&trait; 0563 debug << ')'; 0564 return debug; 0565 } 0566 0567 QDebug operator<<(QDebug debug, const Traits::Arrow &trait) 0568 { 0569 using namespace Traits; 0570 QDebugStateSaver stateSaver(debug); 0571 debug.nospace(); 0572 debug << "Arrow" << '('; 0573 debug << (const void *)&trait; 0574 debug << ')'; 0575 return debug; 0576 } 0577 0578 QDebug operator<<(QDebug debug, const Traits::Text &trait) 0579 { 0580 using namespace Traits; 0581 QDebugStateSaver stateSaver(debug); 0582 debug.nospace(); 0583 debug << "Text" << '('; 0584 debug << (const void *)&trait; 0585 debug << ",\n text=" << trait.text(); 0586 debug << ",\n brush=" << trait.brush; 0587 debug << ",\n font=" << trait.font; 0588 debug << ')'; 0589 return debug; 0590 } 0591 0592 QDebug operator<<(QDebug debug, const Traits::Shadow &trait) 0593 { 0594 using namespace Traits; 0595 QDebugStateSaver stateSaver(debug); 0596 debug.nospace(); 0597 debug << "Shadow" << '('; 0598 debug << (const void *)&trait; 0599 debug << ",\n enabled=" << trait.enabled; 0600 debug << ')'; 0601 return debug; 0602 } 0603 0604 0605 // ImageEffects 0606 0607 QDebug operator<<(QDebug debug, const Traits::ImageEffects::Blur &ref) 0608 { 0609 using namespace Traits::ImageEffects; 0610 QDebugStateSaver stateSaver(debug); 0611 debug.nospace(); 0612 debug << "Blur" << '('; 0613 debug << (const void *)&ref; 0614 debug << ", factor=" << ref.factor; 0615 debug << ')'; 0616 return debug; 0617 } 0618 0619 QDebug operator<<(QDebug debug, const Traits::ImageEffects::Pixelate &ref) 0620 { 0621 using namespace Traits::ImageEffects; 0622 QDebugStateSaver stateSaver(debug); 0623 debug.nospace(); 0624 debug << "Pixelate" << '('; 0625 debug << (const void *)&ref; 0626 debug << ", factor=" << ref.factor; 0627 debug << ')'; 0628 return debug; 0629 } 0630 0631 // Optionals 0632 // clang-format off 0633 #define OPTIONAL_DEBUG_DEF(ClassName)\ 0634 QDebug operator<<(QDebug debug, const Traits::ClassName::Opt &optional)\ 0635 {\ 0636 using namespace Traits;\ 0637 QDebugStateSaver stateSaver(debug);\ 0638 debug.nospace();\ 0639 debug << "Opt" << '<';\ 0640 if (optional.has_value()) {\ 0641 debug << optional.value();\ 0642 } else {\ 0643 debug << #ClassName << "(0x0)";\ 0644 }\ 0645 debug << ">(" << &optional << ')';\ 0646 return debug;\ 0647 } 0648 // clang-format on 0649 OPTIONAL_DEBUG_DEF(Geometry) 0650 OPTIONAL_DEBUG_DEF(Stroke) 0651 OPTIONAL_DEBUG_DEF(Fill) 0652 OPTIONAL_DEBUG_DEF(Highlight) 0653 OPTIONAL_DEBUG_DEF(Arrow) 0654 OPTIONAL_DEBUG_DEF(Text) 0655 OPTIONAL_DEBUG_DEF(Shadow) 0656 0657 #undef OPTIONAL_DEBUG_DEF 0658 0659 QDebug operator<<(QDebug debug, const Traits::OptTuple &optTuple) 0660 { 0661 using namespace Traits; 0662 QDebugStateSaver stateSaver(debug); 0663 debug.nospace(); 0664 debug << "OptTuple" << '('; 0665 debug << (const void *)&optTuple; 0666 debug << ",\n " << std::get<Traits::Geometry::Opt>(optTuple); 0667 debug << ",\n " << std::get<Traits::Stroke::Opt>(optTuple); 0668 debug << ",\n " << std::get<Traits::Fill::Opt>(optTuple); 0669 debug << ",\n " << std::get<Traits::Highlight::Opt>(optTuple); 0670 debug << ",\n " << std::get<Traits::Arrow::Opt>(optTuple); 0671 debug << ",\n " << std::get<Traits::Text::Opt>(optTuple); 0672 debug << ",\n " << std::get<Traits::Shadow::Opt>(optTuple); 0673 debug << ')'; 0674 return debug; 0675 }