| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471 |
- // This file is part of Desktop App Toolkit,
- // a set of libraries for developing nice desktop applications.
- //
- // For license and copyright information please follow this link:
- // https://github.com/desktop-app/legal/blob/master/LEGAL
- //
- #include "ui/effects/round_area_with_shadow.h"
- #include "ui/style/style_core.h"
- #include "ui/image/image_prepare.h"
- #include "ui/painter.h"
- namespace Ui {
- namespace {
- constexpr auto kBgCacheIndex = 0;
- constexpr auto kShadowCacheIndex = 0;
- constexpr auto kOverlayMaskCacheIndex = 0;
- constexpr auto kOverlayShadowCacheIndex = 1;
- constexpr auto kOverlayCacheColumsCount = 2;
- constexpr auto kDivider = 4;
- } // namespace
- [[nodiscard]] QImage RoundAreaWithShadow::PrepareImage(QSize size) {
- const auto ratio = style::DevicePixelRatio();
- auto result = QImage(
- size * ratio,
- QImage::Format_ARGB32_Premultiplied);
- result.setDevicePixelRatio(ratio);
- return result;
- }
- [[nodiscard]] QImage RoundAreaWithShadow::PrepareFramesCache(
- QSize frame,
- int columns) {
- static_assert(!(kFramesCount % kDivider));
- return PrepareImage(QSize(
- frame.width() * kDivider * columns,
- frame.height() * kFramesCount / kDivider));
- }
- [[nodiscard]] QRect RoundAreaWithShadow::FrameCacheRect(
- int frameIndex,
- int column,
- QSize frame) {
- const auto ratio = style::DevicePixelRatio();
- const auto origin = QPoint(
- frame.width() * (kDivider * column + (frameIndex % kDivider)),
- frame.height() * (frameIndex / kDivider));
- return QRect(ratio * origin, ratio * frame);
- }
- RoundAreaWithShadow::RoundAreaWithShadow(
- QSize inner,
- QMargins shadow,
- int twiceRadiusMax)
- : _inner({}, inner)
- , _outer(_inner.marginsAdded(shadow).size())
- , _overlay(QRect(
- 0,
- 0,
- std::max(inner.width(), twiceRadiusMax),
- std::max(inner.height(), twiceRadiusMax)).marginsAdded(shadow).size())
- , _cacheBg(PrepareFramesCache(_outer))
- , _shadowParts(PrepareFramesCache(_outer))
- , _overlayCacheParts(PrepareFramesCache(_overlay, kOverlayCacheColumsCount))
- , _overlayMaskScaled(PrepareImage(_overlay))
- , _overlayShadowScaled(PrepareImage(_overlay))
- , _shadowBuffer(PrepareImage(_outer)) {
- _inner.translate(QRect({}, _outer).center() - _inner.center());
- }
- ImageSubrect RoundAreaWithShadow::validateOverlayMask(
- int frameIndex,
- QSize innerSize,
- float64 radius,
- int twiceRadius,
- float64 scale) {
- const auto ratio = style::DevicePixelRatio();
- const auto cached = (scale == 1.);
- const auto full = cached
- ? FrameCacheRect(frameIndex, kOverlayMaskCacheIndex, _overlay)
- : QRect(QPoint(), _overlay * ratio);
- const auto minWidth = twiceRadius + _outer.width() - _inner.width();
- const auto minHeight = twiceRadius + _outer.height() - _inner.height();
- const auto maskSize = QSize(
- std::max(_outer.width(), minWidth),
- std::max(_outer.height(), minHeight));
- const auto result = ImageSubrect{
- cached ? &_overlayCacheParts : &_overlayMaskScaled,
- QRect(full.topLeft(), maskSize * ratio),
- };
- if (cached && _validOverlayMask[frameIndex]) {
- return result;
- }
- auto p = QPainter(result.image.get());
- const auto position = full.topLeft() / ratio;
- p.setCompositionMode(QPainter::CompositionMode_Source);
- p.fillRect(QRect(position, maskSize), Qt::transparent);
- p.setCompositionMode(QPainter::CompositionMode_SourceOver);
- auto hq = PainterHighQualityEnabler(p);
- const auto inner = QRect(position + _inner.topLeft(), innerSize);
- p.setPen(Qt::NoPen);
- p.setBrush(Qt::white);
- if (scale != 1.) {
- const auto center = inner.center();
- p.save();
- p.translate(center);
- p.scale(scale, scale);
- p.translate(-center);
- }
- p.drawRoundedRect(inner, radius, radius);
- if (scale != 1.) {
- p.restore();
- }
- if (cached) {
- _validOverlayMask[frameIndex] = true;
- }
- return result;
- }
- ImageSubrect RoundAreaWithShadow::validateOverlayShadow(
- int frameIndex,
- QSize innerSize,
- float64 radius,
- int twiceRadius,
- float64 scale,
- const ImageSubrect &mask) {
- const auto ratio = style::DevicePixelRatio();
- const auto cached = (scale == 1.);
- const auto full = cached
- ? FrameCacheRect(frameIndex, kOverlayShadowCacheIndex, _overlay)
- : QRect(QPoint(), _overlay * ratio);
- const auto minWidth = twiceRadius + _outer.width() - _inner.width();
- const auto minHeight = twiceRadius + _outer.height() - _inner.height();
- const auto maskSize = QSize(
- std::max(_outer.width(), minWidth),
- std::max(_outer.height(), minHeight));
- const auto result = ImageSubrect{
- cached ? &_overlayCacheParts : &_overlayShadowScaled,
- QRect(full.topLeft(), maskSize * ratio),
- };
- if (cached && _validOverlayShadow[frameIndex]) {
- return result;
- }
- const auto position = full.topLeft() / ratio;
- _overlayShadowScaled.fill(Qt::transparent);
- const auto inner = QRect(_inner.topLeft(), innerSize);
- const auto add = style::ConvertScale(2.5);
- const auto shift = style::ConvertScale(0.5);
- const auto extended = QRectF(inner).marginsAdded({ add, add, add, add });
- {
- auto p = QPainter(&_overlayShadowScaled);
- p.setCompositionMode(QPainter::CompositionMode_Source);
- auto hq = PainterHighQualityEnabler(p);
- p.setPen(Qt::NoPen);
- p.setBrush(_shadow);
- if (scale != 1.) {
- const auto center = inner.center();
- p.translate(center);
- p.scale(scale, scale);
- p.translate(-center);
- }
- p.drawRoundedRect(extended.translated(0, shift), radius, radius);
- p.end();
- }
- _overlayShadowScaled = Images::Blur(std::move(_overlayShadowScaled));
- auto q = Painter(result.image);
- if (result.image != &_overlayShadowScaled) {
- q.setCompositionMode(QPainter::CompositionMode_Source);
- q.drawImage(
- QRect(position, maskSize),
- _overlayShadowScaled,
- QRect(QPoint(), maskSize * ratio));
- }
- q.setCompositionMode(QPainter::CompositionMode_DestinationOut);
- q.drawImage(QRect(position, maskSize), *mask.image, mask.rect);
- if (cached) {
- _validOverlayShadow[frameIndex] = true;
- }
- return result;
- }
- void RoundAreaWithShadow::overlayExpandedBorder(
- QPainter &p,
- QSize size,
- float64 expandRatio,
- float64 radiusFrom,
- float64 radiusTill,
- float64 scale) {
- const auto progress = expandRatio;
- const auto frame = int(base::SafeRound(progress * (kFramesCount - 1)));
- const auto cacheRatio = frame / float64(kFramesCount - 1);
- const auto radius = radiusFrom + (radiusTill - radiusFrom) * cacheRatio;
- const auto twiceRadius = int(base::SafeRound(radius * 2));
- const auto innerSize = QSize(
- std::max(_inner.width(), twiceRadius),
- std::max(_inner.height(), twiceRadius));
- const auto overlayMask = validateOverlayMask(
- frame,
- innerSize,
- radius,
- twiceRadius,
- scale);
- const auto overlayShadow = validateOverlayShadow(
- frame,
- innerSize,
- radius,
- twiceRadius,
- scale,
- overlayMask);
- p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
- FillWithImage(p, QRect(QPoint(), size), overlayMask);
- p.setCompositionMode(QPainter::CompositionMode_SourceOver);
- FillWithImage(p, QRect(QPoint(), size), overlayShadow);
- }
- QRect RoundAreaWithShadow::FillWithImage(
- QPainter &p,
- QRect geometry,
- const ImageSubrect &pattern) {
- const auto factor = style::DevicePixelRatio();
- const auto &image = *pattern.image;
- const auto source = pattern.rect;
- const auto sourceWidth = (source.width() / factor);
- const auto sourceHeight = (source.height() / factor);
- if (geometry.width() == sourceWidth) {
- const auto part = (sourceHeight / 2) - 1;
- const auto fill = geometry.height() - 2 * part;
- const auto half = part * factor;
- const auto top = source.height() - half;
- p.drawImage(
- geometry.topLeft(),
- image,
- QRect(source.x(), source.y(), source.width(), half));
- if (fill > 0) {
- p.drawImage(
- QRect(
- geometry.topLeft() + QPoint(0, part),
- QSize(sourceWidth, fill)),
- image,
- QRect(
- source.x(),
- source.y() + half,
- source.width(),
- top - half));
- }
- p.drawImage(
- geometry.topLeft() + QPoint(0, part + fill),
- image,
- QRect(source.x(), source.y() + top, source.width(), half));
- return QRect();
- } else if (geometry.height() == sourceHeight) {
- const auto part = (sourceWidth / 2) - 1;
- const auto fill = geometry.width() - 2 * part;
- const auto half = part * factor;
- const auto left = source.width() - half;
- p.drawImage(
- geometry.topLeft(),
- image,
- QRect(source.x(), source.y(), half, source.height()));
- if (fill > 0) {
- p.drawImage(
- QRect(
- geometry.topLeft() + QPoint(part, 0),
- QSize(fill, sourceHeight)),
- image,
- QRect(
- source.x() + half,
- source.y(),
- left - half,
- source.height()));
- }
- p.drawImage(
- geometry.topLeft() + QPoint(part + fill, 0),
- image,
- QRect(source.x() + left, source.y(), half, source.height()));
- return QRect();
- } else if (geometry.width() > sourceWidth
- && geometry.height() > sourceHeight) {
- const auto xpart = (sourceWidth / 2) - 1;
- const auto xfill = geometry.width() - 2 * xpart;
- const auto xhalf = xpart * factor;
- const auto left = source.width() - xhalf;
- const auto ypart = (sourceHeight / 2) - 1;
- const auto yfill = geometry.height() - 2 * ypart;
- const auto yhalf = ypart * factor;
- const auto top = source.height() - yhalf;
- p.drawImage(
- geometry.topLeft(),
- image,
- QRect(source.x(), source.y(), xhalf, yhalf));
- if (xfill > 0) {
- p.drawImage(
- QRect(
- geometry.topLeft() + QPoint(xpart, 0),
- QSize(xfill, ypart)),
- image,
- QRect(
- source.x() + xhalf,
- source.y(),
- left - xhalf,
- yhalf));
- }
- p.drawImage(
- geometry.topLeft() + QPoint(xpart + xfill, 0),
- image,
- QRect(source.x() + left, source.y(), xhalf, yhalf));
- if (yfill > 0) {
- p.drawImage(
- QRect(
- geometry.topLeft() + QPoint(0, ypart),
- QSize(xpart, yfill)),
- image,
- QRect(
- source.x(),
- source.y() + yhalf,
- xhalf,
- top - yhalf));
- p.drawImage(
- QRect(
- geometry.topLeft() + QPoint(xpart + xfill, ypart),
- QSize(xpart, yfill)),
- image,
- QRect(
- source.x() + left,
- source.y() + yhalf,
- xhalf,
- top - yhalf));
- }
- p.drawImage(
- geometry.topLeft() + QPoint(0, ypart + yfill),
- image,
- QRect(source.x(), source.y() + top, xhalf, yhalf));
- if (xfill > 0) {
- p.drawImage(
- QRect(
- geometry.topLeft() + QPoint(xpart, ypart + yfill),
- QSize(xfill, ypart)),
- image,
- QRect(
- source.x() + xhalf,
- source.y() + top,
- left - xhalf,
- yhalf));
- }
- p.drawImage(
- geometry.topLeft() + QPoint(xpart + xfill, ypart + yfill),
- image,
- QRect(source.x() + left, source.y() + top, xhalf, yhalf));
- return QRect(
- geometry.topLeft() + QPoint(xpart, ypart),
- QSize(xfill, yfill));
- } else {
- Unexpected("Values in RoundAreaWithShadow::fillWithImage.");
- }
- }
- void RoundAreaWithShadow::setShadowColor(const QColor &shadow) {
- if (_shadow == shadow) {
- return;
- }
- _shadow = shadow;
- ranges::fill(_validBg, false);
- ranges::fill(_validShadow, false);
- ranges::fill(_validOverlayShadow, false);
- }
- QRect RoundAreaWithShadow::validateShadow(
- int frameIndex,
- float64 scale,
- float64 radius) {
- const auto rect = FrameCacheRect(frameIndex, kShadowCacheIndex, _outer);
- if (_validShadow[frameIndex]) {
- return rect;
- }
- _shadowBuffer.fill(Qt::transparent);
- auto p = QPainter(&_shadowBuffer);
- auto hq = PainterHighQualityEnabler(p);
- const auto center = _inner.center();
- const auto add = style::ConvertScale(2.5);
- const auto shift = style::ConvertScale(0.5);
- const auto big = QRectF(_inner).marginsAdded({ add, add, add, add });
- p.setPen(Qt::NoPen);
- p.setBrush(_shadow);
- if (scale != 1.) {
- p.translate(center);
- p.scale(scale, scale);
- p.translate(-center);
- }
- p.drawRoundedRect(big.translated(0, shift), radius, radius);
- p.end();
- _shadowBuffer = Images::Blur(std::move(_shadowBuffer));
- auto q = QPainter(&_shadowParts);
- q.setCompositionMode(QPainter::CompositionMode_Source);
- q.drawImage(rect.topLeft() / style::DevicePixelRatio(), _shadowBuffer);
- _validShadow[frameIndex] = true;
- return rect;
- }
- void RoundAreaWithShadow::setBackgroundColor(const QColor &background) {
- if (_background == background) {
- return;
- }
- _background = background;
- ranges::fill(_validBg, false);
- }
- ImageSubrect RoundAreaWithShadow::validateFrame(
- int frameIndex,
- float64 scale,
- float64 radius) {
- const auto result = ImageSubrect{
- &_cacheBg,
- FrameCacheRect(frameIndex, kBgCacheIndex, _outer)
- };
- if (_validBg[frameIndex]) {
- return result;
- }
- const auto position = result.rect.topLeft() / style::DevicePixelRatio();
- const auto inner = _inner.translated(position);
- const auto shadowSource = validateShadow(frameIndex, scale, radius);
- auto p = QPainter(&_cacheBg);
- p.setCompositionMode(QPainter::CompositionMode_Source);
- p.drawImage(position, _shadowParts, shadowSource);
- p.setCompositionMode(QPainter::CompositionMode_SourceOver);
- auto hq = PainterHighQualityEnabler(p);
- p.setPen(Qt::NoPen);
- p.setBrush(_background);
- if (scale != 1.) {
- const auto center = inner.center();
- p.save();
- p.translate(center);
- p.scale(scale, scale);
- p.translate(-center);
- }
- p.drawRoundedRect(inner, radius, radius);
- if (scale != 1.) {
- p.restore();
- }
- _validBg[frameIndex] = true;
- return result;
- }
- } // namespace Ui
|