tdesktop/Telegram/SourceFiles/ui/effects/round_checkbox.cpp

321 lines
11 KiB
C++

/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "ui/effects/round_checkbox.h"
namespace Ui {
namespace {
static constexpr int kWideScale = 3;
void prepareCheckCaches(const style::RoundCheckbox *st, bool displayInactive, QPixmap &checkBgCache, QPixmap &checkFullCache) {
auto size = st->size;
auto wideSize = size * kWideScale;
auto cache = QImage(wideSize * cIntRetinaFactor(), wideSize * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
cache.setDevicePixelRatio(cRetinaFactor());
cache.fill(Qt::transparent);
{
Painter p(&cache);
PainterHighQualityEnabler hq(p);
if (displayInactive) {
p.setPen(Qt::NoPen);
} else {
auto pen = st->border->p;
pen.setWidth(st->width);
p.setPen(pen);
}
p.setBrush(st->bgActive);
auto ellipse = QRect((wideSize - size) / 2, (wideSize - size) / 2, size, size);
p.drawEllipse(ellipse);
}
auto cacheIcon = cache;
{
Painter p(&cacheIcon);
auto ellipse = QRect((wideSize - size) / 2, (wideSize - size) / 2, size, size);
st->check.paint(p, ellipse.topLeft(), wideSize);
}
checkBgCache = App::pixmapFromImageInPlace(std::move(cache));
checkFullCache = App::pixmapFromImageInPlace(std::move(cacheIcon));
}
} // namespace
RoundCheckbox::RoundCheckbox(const style::RoundCheckbox &st, base::lambda<void()> updateCallback)
: _st(st)
, _updateCallback(updateCallback) {
}
QRect RoundCheckbox::cacheDestRect(int x, int y, float64 scale) const {
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);
}
void RoundCheckbox::paint(Painter &p, TimeMs ms, int x, int y, int outerWidth, float64 masterScale) {
for (auto &icon : _icons) {
icon.fadeIn.step(ms);
icon.fadeOut.step(ms);
}
removeFadeOutedIcons();
auto cacheSize = kWideScale * _st.size * cIntRetinaFactor();
auto cacheFrom = QRect(0, 0, cacheSize, cacheSize);
auto displayInactive = !_inactiveCacheBg.isNull();
auto inactiveTo = cacheDestRect(x, y, masterScale);
PainterHighQualityEnabler hq(p);
if (!_inactiveCacheBg.isNull()) {
p.drawPixmap(inactiveTo, _inactiveCacheBg, cacheFrom);
}
for (auto &icon : _icons) {
auto fadeIn = icon.fadeIn.current(1.);
auto fadeOut = icon.fadeOut.current(1.);
auto to = cacheDestRect(x, y, (1. - (1. - _st.sizeSmall) * (1. - fadeOut)) * masterScale);
p.setOpacity(fadeIn * fadeOut);
if (fadeOut < 1.) {
p.drawPixmapLeft(to, outerWidth, icon.wideCheckCache, cacheFrom);
} else if (fadeIn == 1.) {
p.drawPixmapLeft(to, outerWidth, _wideCheckFullCache, cacheFrom);
} else {
auto realDivider = ((kWideScale - 1) * _st.size / 2 + qMax(fadeIn - 0.5, 0.) * 2. * _st.size);
auto divider = qRound(realDivider * masterScale);
auto cacheDivider = qRound(realDivider) * cIntRetinaFactor();
p.drawPixmapLeft(QRect(to.x(), to.y(), divider, to.height()), outerWidth, _wideCheckFullCache, QRect(0, 0, cacheDivider, cacheFrom.height()));
p.drawPixmapLeft(QRect(to.x() + divider, to.y(), to.width() - divider, to.height()), outerWidth, _wideCheckBgCache, QRect(cacheDivider, 0, cacheFrom.width() - cacheDivider, _wideCheckBgCache.height()));
}
}
p.setOpacity(1.);
if (!_inactiveCacheFg.isNull()) {
p.drawPixmap(inactiveTo, _inactiveCacheFg, cacheFrom);
}
}
void RoundCheckbox::setChecked(bool newChecked, SetStyle speed) {
if (_checked == newChecked) {
if (speed != SetStyle::Animated && !_icons.empty()) {
_icons.back().fadeIn.finish();
_icons.back().fadeOut.finish();
}
return;
}
_checked = newChecked;
if (_checked) {
if (_wideCheckBgCache.isNull()) {
prepareCheckCaches(&_st, _displayInactive, _wideCheckBgCache, _wideCheckFullCache);
}
_icons.push_back(Icon());
_icons.back().fadeIn.start(_updateCallback, 0, 1, _st.duration);
if (speed != SetStyle::Animated) {
_icons.back().fadeIn.finish();
}
} else {
if (speed == SetStyle::Animated) {
prepareWideCheckIconCache(&_icons.back());
}
_icons.back().fadeOut.start(_updateCallback, 1, 0, _st.duration);
if (speed != SetStyle::Animated) {
_icons.back().fadeOut.finish();
}
}
}
void RoundCheckbox::invalidateCache() {
if (!_wideCheckBgCache.isNull() || !_wideCheckFullCache.isNull()) {
prepareCheckCaches(&_st, _displayInactive, _wideCheckBgCache, _wideCheckFullCache);
}
if (!_inactiveCacheBg.isNull() || !_inactiveCacheFg.isNull()) {
prepareInactiveCache();
}
}
void RoundCheckbox::setDisplayInactive(bool displayInactive) {
if (_displayInactive != displayInactive) {
_displayInactive = displayInactive;
if (_displayInactive) {
prepareInactiveCache();
} else {
_inactiveCacheBg = _inactiveCacheFg = QPixmap();
}
if (!_wideCheckBgCache.isNull()) {
prepareCheckCaches(&_st, _displayInactive, _wideCheckBgCache, _wideCheckFullCache);
}
for (auto &icon : _icons) {
if (!icon.wideCheckCache.isNull()) {
prepareWideCheckIconCache(&icon);
}
}
}
}
void RoundCheckbox::removeFadeOutedIcons() {
while (!_icons.empty() && !_icons.front().fadeIn.animating() && !_icons.front().fadeOut.animating()) {
if (_icons.size() > 1 || !_checked) {
_icons.erase(_icons.begin());
} else {
break;
}
}
}
void RoundCheckbox::prepareWideCheckIconCache(Icon *icon) {
auto cacheWidth = _wideCheckBgCache.width() / _wideCheckBgCache.devicePixelRatio();
auto cacheHeight = _wideCheckBgCache.height() / _wideCheckBgCache.devicePixelRatio();
auto wideCache = QImage(cacheWidth * cIntRetinaFactor(), cacheHeight * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
wideCache.setDevicePixelRatio(cRetinaFactor());
{
Painter p(&wideCache);
p.setCompositionMode(QPainter::CompositionMode_Source);
auto iconSize = kWideScale * _st.size;
auto realDivider = ((kWideScale - 1) * _st.size / 2 + qMax(icon->fadeIn.current(1.) - 0.5, 0.) * 2. * _st.size);
auto divider = qRound(realDivider);
auto cacheDivider = qRound(realDivider) * cIntRetinaFactor();
p.drawPixmapLeft(QRect(0, 0, divider, iconSize), cacheWidth, _wideCheckFullCache, QRect(0, 0, divider * cIntRetinaFactor(), _wideCheckFullCache.height()));
p.drawPixmapLeft(QRect(divider, 0, iconSize - divider, iconSize), cacheWidth, _wideCheckBgCache, QRect(cacheDivider, 0, _wideCheckBgCache.width() - cacheDivider, _wideCheckBgCache.height()));
}
icon->wideCheckCache = App::pixmapFromImageInPlace(std::move(wideCache));
icon->wideCheckCache.setDevicePixelRatio(cRetinaFactor());
}
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 * cIntRetinaFactor(), wideSize * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
cacheBg.setDevicePixelRatio(cRetinaFactor());
cacheBg.fill(Qt::transparent);
auto cacheFg = cacheBg;
if (_st.bgInactive) {
Painter p(&cacheBg);
PainterHighQualityEnabler hq(p);
p.setPen(Qt::NoPen);
p.setBrush(_st.bgInactive);
p.drawEllipse(ellipse);
}
_inactiveCacheBg = App::pixmapFromImageInPlace(std::move(cacheBg));
{
Painter p(&cacheFg);
PainterHighQualityEnabler hq(p);
auto pen = _st.border->p;
pen.setWidth(_st.width);
p.setPen(pen);
p.setBrush(Qt::NoBrush);
p.drawEllipse(ellipse);
}
_inactiveCacheFg = App::pixmapFromImageInPlace(std::move(cacheFg));
}
RoundImageCheckbox::RoundImageCheckbox(const style::RoundImageCheckbox &st, base::lambda<void()> updateCallback, PaintRoundImage &&paintRoundImage)
: _st(st)
, _updateCallback(updateCallback)
, _paintRoundImage(std::move(paintRoundImage))
, _check(_st.check, _updateCallback) {
}
void RoundImageCheckbox::paint(Painter &p, TimeMs ms, int x, int y, int outerWidth) {
_selection.step(ms);
auto selectionLevel = _selection.current(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 {
if (!_wideCache.isNull()) {
_wideCache = QPixmap();
}
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(snap(selectionLevel, 0., 1.));
p.setBrush(Qt::NoBrush);
auto pen = _st.selectFg->p;
pen.setWidth(_st.selectWidth);
p.setPen(pen);
p.drawEllipse(rtlrect(x, y, _st.imageRadius * 2, _st.imageRadius * 2, outerWidth));
p.setOpacity(1.);
}
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, ms, iconLeft, iconTop, outerWidth);
}
float64 RoundImageCheckbox::checkedAnimationRatio() const {
return snap(_selection.current(checked() ? 1. : 0.), 0., 1.);
}
void RoundImageCheckbox::setChecked(bool newChecked, SetStyle speed) {
auto changed = (checked() != newChecked);
_check.setChecked(newChecked, speed);
if (!changed) {
if (speed != SetStyle::Animated) {
_selection.finish();
}
return;
}
if (speed == SetStyle::Animated) {
prepareWideCache();
_selection.start(_updateCallback, checked() ? 0 : 1, checked() ? 1 : 0, _st.selectDuration, anim::bumpy(1.25));
} else {
_selection.finish();
}
}
void RoundImageCheckbox::prepareWideCache() {
if (_wideCache.isNull()) {
auto size = _st.imageRadius * 2;
auto wideSize = size * kWideScale;
QImage cache(wideSize * cIntRetinaFactor(), wideSize * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
cache.setDevicePixelRatio(cRetinaFactor());
{
Painter p(&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 = App::pixmapFromImageInPlace(std::move(cache));
}
}
} // namespace Ui