| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503 |
- /*
- This file is part of Telegram Desktop,
- the official desktop application for the Telegram messaging service.
- For license and copyright information please follow this link:
- https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
- */
- #include "ui/effects/round_checkbox.h"
- #include "ui/rp_widget.h"
- #include "ui/ui_utility.h"
- #include "ui/painter.h"
- #include "ui/effects/outline_segments.h"
- #include "styles/style_widgets.h"
- #include <QtCore/QCoreApplication>
- namespace Ui {
- namespace {
- constexpr auto kAnimationTimerDelta = crl::time(7);
- constexpr auto kWideScale = 3;
- [[nodiscard]] int CountFramesCount(const style::RoundCheckbox *st) {
- return (st->duration / kAnimationTimerDelta) + 1;
- }
- class CheckCaches : public QObject {
- public:
- CheckCaches(QObject *parent) : QObject(parent) {
- Expects(parent != nullptr);
- style::PaletteChanged(
- ) | rpl::start_with_next([=] {
- _data.clear();
- }, _lifetime);
- }
- QPixmap frame(
- const style::RoundCheckbox *st,
- bool displayInactive,
- float64 progress);
- private:
- struct Frames {
- bool displayInactive = false;
- std::vector<QPixmap> list;
- QPixmap outerWide;
- QPixmap inner;
- QPixmap check;
- };
- Frames &framesForStyle(
- const style::RoundCheckbox *st,
- bool displayInactive);
- void prepareFramesData(
- const style::RoundCheckbox *st,
- bool displayInactive,
- Frames &frames);
- QPixmap paintFrame(
- const style::RoundCheckbox *st,
- const Frames &frames,
- float64 progress);
- std::map<const style::RoundCheckbox *, Frames> _data;
- rpl::lifetime _lifetime;
- };
- QPixmap PrepareOuterWide(const style::RoundCheckbox *st) {
- const auto size = st->size;
- const auto wideSize = size * kWideScale;
- auto result = QImage(
- QSize(wideSize, wideSize) * style::DevicePixelRatio(),
- QImage::Format_ARGB32_Premultiplied);
- result.setDevicePixelRatio(style::DevicePixelRatio());
- result.fill(Qt::transparent);
- {
- auto p = QPainter(&result);
- PainterHighQualityEnabler hq(p);
- p.setPen(Qt::NoPen);
- p.setBrush(st->border);
- const auto half = st->width / 2.;
- p.drawEllipse(QRectF(
- (wideSize - size) / 2 - half,
- (wideSize - size) / 2 - half,
- size + 2. * half,
- size + 2. * half));
- }
- return Ui::PixmapFromImage(std::move(result));
- }
- QPixmap PrepareInner(const style::RoundCheckbox *st, bool displayInactive) {
- const auto size = st->size;
- auto result = QImage(
- QSize(size, size) * style::DevicePixelRatio(),
- QImage::Format_ARGB32_Premultiplied);
- result.setDevicePixelRatio(style::DevicePixelRatio());
- result.fill(Qt::transparent);
- {
- auto p = QPainter(&result);
- PainterHighQualityEnabler hq(p);
- p.setPen(Qt::NoPen);
- p.setBrush(st->bgActive);
- const auto half = st->width / 2.;
- p.drawEllipse(QRectF(
- displayInactive ? 0. : half,
- displayInactive ? 0. : half,
- size - (displayInactive ? 0. : 2. * half),
- size - (displayInactive ? 0. : 2. * half)));
- }
- return Ui::PixmapFromImage(std::move(result));
- }
- QPixmap PrepareCheck(const style::RoundCheckbox *st) {
- const auto size = st->size;
- auto result = QImage(
- QSize(size, size) * style::DevicePixelRatio(),
- QImage::Format_ARGB32_Premultiplied);
- result.setDevicePixelRatio(style::DevicePixelRatio());
- result.fill(Qt::transparent);
- {
- auto p = QPainter(&result);
- st->check.paint(p, 0, 0, size);
- }
- return Ui::PixmapFromImage(std::move(result));
- }
- QRect WideDestRect(
- const style::RoundCheckbox *st,
- int x,
- int y,
- float64 scale) {
- auto iconSizeFull = kWideScale * st->size;
- auto iconSize = qRound(iconSizeFull * scale);
- if (iconSize % 2 != iconSizeFull % 2) {
- ++iconSize;
- }
- auto iconShift = (iconSizeFull - iconSize) / 2;
- auto iconLeft = x - (kWideScale - 1) * st->size / 2 + iconShift;
- auto iconTop = y - (kWideScale - 1) * st->size / 2 + iconShift;
- return QRect(iconLeft, iconTop, iconSize, iconSize);
- }
- CheckCaches::Frames &CheckCaches::framesForStyle(
- const style::RoundCheckbox *st,
- bool displayInactive) {
- auto i = _data.find(st);
- if (i == _data.end()) {
- i = _data.emplace(st, Frames()).first;
- prepareFramesData(st, displayInactive, i->second);
- } else if (i->second.displayInactive != displayInactive) {
- i->second = Frames();
- prepareFramesData(st, displayInactive, i->second);
- }
- return i->second;
- }
- void CheckCaches::prepareFramesData(
- const style::RoundCheckbox *st,
- bool displayInactive,
- Frames &frames) {
- frames.list.resize(CountFramesCount(st));
- frames.displayInactive = displayInactive;
- if (!frames.displayInactive) {
- frames.outerWide = PrepareOuterWide(st);
- }
- frames.inner = PrepareInner(st, frames.displayInactive);
- frames.check = PrepareCheck(st);
- }
- QPixmap CheckCaches::frame(
- const style::RoundCheckbox *st,
- bool displayInactive,
- float64 progress) {
- auto &frames = framesForStyle(st, displayInactive);
- const auto frameCount = int(frames.list.size());
- const auto frameIndex = int(base::SafeRound(progress * (frameCount - 1)));
- Assert(frameIndex >= 0 && frameIndex < frameCount);
- if (!frames.list[frameIndex]) {
- const auto frameProgress = frameIndex / float64(frameCount - 1);
- frames.list[frameIndex] = paintFrame(st, frames, frameProgress);
- }
- return frames.list[frameIndex];
- }
- QPixmap CheckCaches::paintFrame(
- const style::RoundCheckbox *st,
- const Frames &frames,
- float64 progress) {
- const auto size = st->size;
- const auto wideSize = size * kWideScale;
- const auto skip = (wideSize - size) / 2;
- auto result = QImage(wideSize * style::DevicePixelRatio(), wideSize * style::DevicePixelRatio(), QImage::Format_ARGB32_Premultiplied);
- result.setDevicePixelRatio(style::DevicePixelRatio());
- result.fill(Qt::transparent);
- const auto roundProgress = (progress >= st->bgDuration)
- ? 1.
- : (progress / st->bgDuration);
- const auto checkProgress = (1. - progress >= st->fgDuration)
- ? 0.
- : (1. - (1. - progress) / st->fgDuration);
- {
- auto p = QPainter(&result);
- PainterHighQualityEnabler hq(p);
- if (!frames.displayInactive) {
- const auto outerMaxScale = (size - st->width) / float64(size);
- const auto outerScale = roundProgress
- + (1. - roundProgress) * outerMaxScale;
- const auto outerTo = WideDestRect(st, skip, skip, outerScale);
- const auto outerFrom = QRect(
- QPoint(0, 0),
- QSize(wideSize, wideSize) * style::DevicePixelRatio());
- p.drawPixmap(outerTo, frames.outerWide, outerFrom);
- }
- p.drawPixmap(skip, skip, frames.inner);
- const auto divider = checkProgress * st->size;
- const auto checkTo = QRect(skip, skip, divider, st->size);
- const auto checkFrom = QRect(
- QPoint(0, 0),
- QSize(divider, st->size) * style::DevicePixelRatio());
- p.drawPixmap(checkTo, frames.check, checkFrom);
- p.setCompositionMode(QPainter::CompositionMode_Source);
- p.setPen(Qt::NoPen);
- p.setBrush(Qt::transparent);
- const auto remove = size * (1. - roundProgress);
- p.drawEllipse(QRectF(
- (wideSize - remove) / 2.,
- (wideSize - remove) / 2.,
- remove,
- remove));
- }
- return Ui::PixmapFromImage(std::move(result));
- }
- CheckCaches *FrameCaches() {
- static QPointer<CheckCaches> Instance;
- if (const auto instance = Instance.data()) {
- return instance;
- }
- const auto result = new CheckCaches(QCoreApplication::instance());
- Instance = result;
- return result;
- }
- } // namespace
- RoundCheckbox::RoundCheckbox(const style::RoundCheckbox &st, Fn<void()> updateCallback)
- : _st(st)
- , _updateCallback(updateCallback) {
- }
- void RoundCheckbox::paint(QPainter &p, int x, int y, int outerWidth, float64 masterScale) const {
- if (!_st.size
- || (!_checkedProgress.animating()
- && !_checked
- && !_displayInactive)) {
- return;
- }
- auto cacheSize = kWideScale * _st.size * style::DevicePixelRatio();
- auto cacheFrom = QRect(0, 0, cacheSize, cacheSize);
- auto inactiveTo = WideDestRect(&_st, x, y, masterScale);
- PainterHighQualityEnabler hq(p);
- if (!_inactiveCacheBg.isNull()) {
- p.drawPixmap(inactiveTo, _inactiveCacheBg, cacheFrom);
- }
- const auto progress = _checkedProgress.value(_checked ? 1. : 0.);
- if (progress > 0.) {
- auto frame = FrameCaches()->frame(&_st, _displayInactive, progress);
- p.drawPixmap(inactiveTo, frame, cacheFrom);
- }
- if (!_inactiveCacheFg.isNull()) {
- p.drawPixmap(inactiveTo, _inactiveCacheFg, cacheFrom);
- }
- }
- void RoundCheckbox::setChecked(bool newChecked, anim::type animated) {
- if (_checked == newChecked) {
- if (animated == anim::type::instant) {
- _checkedProgress.stop();
- }
- return;
- }
- _checked = newChecked;
- if (animated == anim::type::normal) {
- _checkedProgress.start(
- _updateCallback,
- _checked ? 0. : 1.,
- _checked ? 1. : 0.,
- _st.duration,
- anim::linear);
- } else {
- _checkedProgress.stop();
- }
- }
- void RoundCheckbox::invalidateCache() {
- if (!_inactiveCacheBg.isNull() || !_inactiveCacheFg.isNull()) {
- prepareInactiveCache();
- }
- }
- void RoundCheckbox::setDisplayInactive(bool displayInactive) {
- if (_displayInactive != displayInactive) {
- _displayInactive = displayInactive;
- if (_displayInactive) {
- prepareInactiveCache();
- } else {
- _inactiveCacheBg = _inactiveCacheFg = QPixmap();
- }
- }
- }
- void RoundCheckbox::prepareInactiveCache() {
- auto wideSize = _st.size * kWideScale;
- auto ellipse = QRect((wideSize - _st.size) / 2, (wideSize - _st.size) / 2, _st.size, _st.size);
- auto cacheBg = QImage(wideSize * style::DevicePixelRatio(), wideSize * style::DevicePixelRatio(), QImage::Format_ARGB32_Premultiplied);
- cacheBg.setDevicePixelRatio(style::DevicePixelRatio());
- cacheBg.fill(Qt::transparent);
- auto cacheFg = cacheBg;
- if (_st.bgInactive) {
- auto p = QPainter(&cacheBg);
- PainterHighQualityEnabler hq(p);
- p.setPen(Qt::NoPen);
- p.setBrush(_st.bgInactive);
- p.drawEllipse(ellipse);
- }
- _inactiveCacheBg = Ui::PixmapFromImage(std::move(cacheBg));
- {
- auto p = QPainter(&cacheFg);
- PainterHighQualityEnabler hq(p);
- auto pen = _st.border->p;
- pen.setWidth(_st.width);
- p.setPen(pen);
- p.setBrush(Qt::NoBrush);
- p.drawEllipse(ellipse);
- }
- _inactiveCacheFg = Ui::PixmapFromImage(std::move(cacheFg));
- }
- RoundImageCheckbox::RoundImageCheckbox(
- const style::RoundImageCheckbox &st,
- Fn<void()> updateCallback,
- PaintRoundImage &&paintRoundImage,
- Fn<std::optional<int>(int size)> roundingRadius)
- : _st(st)
- , _updateCallback(updateCallback)
- , _paintRoundImage(std::move(paintRoundImage))
- , _roundingRadius(std::move(roundingRadius))
- , _check(_st.check, _updateCallback) {
- }
- RoundImageCheckbox::RoundImageCheckbox(RoundImageCheckbox&&) = default;
- RoundImageCheckbox::~RoundImageCheckbox() = default;
- void RoundImageCheckbox::paint(Painter &p, int x, int y, int outerWidth) const {
- auto selectionLevel = _selection.value(checked() ? 1. : 0.);
- if (_selection.animating()) {
- auto userpicRadius = qRound(kWideScale * (_st.imageRadius + (_st.imageSmallRadius - _st.imageRadius) * selectionLevel));
- auto userpicShift = kWideScale * _st.imageRadius - userpicRadius;
- auto userpicLeft = x - (kWideScale - 1) * _st.imageRadius + userpicShift;
- auto userpicTop = y - (kWideScale - 1) * _st.imageRadius + userpicShift;
- auto to = QRect(userpicLeft, userpicTop, userpicRadius * 2, userpicRadius * 2);
- auto from = QRect(QPoint(0, 0), _wideCache.size());
- PainterHighQualityEnabler hq(p);
- p.drawPixmapLeft(to, outerWidth, _wideCache, from);
- } else {
- auto userpicRadius = checked() ? _st.imageSmallRadius : _st.imageRadius;
- auto userpicShift = _st.imageRadius - userpicRadius;
- auto userpicLeft = x + userpicShift;
- auto userpicTop = y + userpicShift;
- _paintRoundImage(p, userpicLeft, userpicTop, outerWidth, userpicRadius * 2);
- }
- if (selectionLevel > 0) {
- PainterHighQualityEnabler hq(p);
- p.setOpacity(std::clamp(selectionLevel, 0., 1.));
- p.setBrush(Qt::NoBrush);
- const auto segments = int(_segments.size());
- const auto rect = style::rtlrect(
- x,
- y,
- _st.imageRadius * 2,
- _st.imageRadius * 2,
- outerWidth);
- const auto add = _st.selectExtendTwice / 2.;
- const auto outline = QRectF(rect).marginsAdded({
- add, add, add, add });
- if (segments < 2) {
- const auto radius = _roundingRadius
- ? _roundingRadius(_st.imageRadius * 2)
- : std::optional<int>();
- const auto pen = QPen(
- segments ? _segments.front().brush : _st.selectFg->b,
- segments ? _segments.front().width : _st.selectWidth);
- p.setPen(pen);
- if (!radius) {
- p.drawEllipse(outline);
- } else {
- p.drawRoundedRect(outline, *radius, *radius);
- }
- } else {
- PaintOutlineSegments(p, outline, _segments);
- }
- p.setOpacity(1.);
- }
- if (_st.check.size > 0) {
- auto iconLeft = x + 2 * _st.imageRadius + _st.selectWidth - _st.check.size;
- auto iconTop = y + 2 * _st.imageRadius + _st.selectWidth - _st.check.size;
- _check.paint(p, iconLeft, iconTop, outerWidth);
- }
- }
- float64 RoundImageCheckbox::checkedAnimationRatio() const {
- return std::clamp(_selection.value(checked() ? 1. : 0.), 0., 1.);
- }
- void RoundImageCheckbox::setChecked(bool newChecked, anim::type animated) {
- auto changed = (checked() != newChecked);
- _check.setChecked(newChecked, animated);
- if (!changed) {
- if (animated == anim::type::instant) {
- _selection.stop();
- _wideCache = QPixmap();
- }
- return;
- }
- if (animated == anim::type::normal) {
- prepareWideCache();
- const auto from = checked() ? 0. : 1.;
- const auto to = checked() ? 1. : 0.;
- _selection.start(
- [=](float64 value) {
- if (_updateCallback) {
- _updateCallback();
- }
- if (value == to) {
- _wideCache = QPixmap();
- }
- },
- from,
- to,
- _st.selectDuration,
- anim::bumpy(1.25));
- } else {
- _selection.stop();
- _wideCache = QPixmap();
- }
- }
- void RoundImageCheckbox::prepareWideCache() {
- if (_wideCache.isNull()) {
- auto size = _st.imageRadius * 2;
- auto wideSize = size * kWideScale;
- QImage cache(wideSize * style::DevicePixelRatio(), wideSize * style::DevicePixelRatio(), QImage::Format_ARGB32_Premultiplied);
- cache.setDevicePixelRatio(style::DevicePixelRatio());
- {
- auto p = Painter(&cache);
- p.setCompositionMode(QPainter::CompositionMode_Source);
- p.fillRect(0, 0, wideSize, wideSize, Qt::transparent);
- p.setCompositionMode(QPainter::CompositionMode_SourceOver);
- _paintRoundImage(p, (wideSize - size) / 2, (wideSize - size) / 2, wideSize, size);
- }
- _wideCache = Ui::PixmapFromImage(std::move(cache));
- }
- }
- void RoundImageCheckbox::setColorOverride(std::optional<QBrush> fg) {
- if (fg) {
- setCustomizedSegments({
- { .brush = *fg, .width = float64(_st.selectWidth) }
- });
- } else {
- setCustomizedSegments({});
- }
- }
- void RoundImageCheckbox::setCustomizedSegments(
- std::vector<Ui::OutlineSegment> segments) {
- _segments = std::move(segments);
- }
- } // namespace Ui
|