/* 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/special_buttons.h" #include "styles/style_boxes.h" #include "styles/style_history.h" #include "dialogs/dialogs_layout.h" #include "ui/effects/ripple_animation.h" #include "ui/effects/radial_animation.h" #include "ui/image/image_prepare.h" #include "ui/empty_userpic.h" #include "data/data_photo.h" #include "data/data_session.h" #include "data/data_feed.h" #include "data/data_channel.h" #include "history/history.h" #include "core/file_utilities.h" #include "core/application.h" #include "boxes/photo_crop_box.h" #include "boxes/confirm_box.h" #include "window/window_controller.h" #include "lang/lang_keys.h" #include "auth_session.h" #include "apiwrap.h" #include "mainwidget.h" #include "observer_peer.h" namespace Ui { namespace { constexpr int kWideScale = 5; QString CropTitle(not_null peer) { if (peer->isChat() || peer->isMegagroup()) { return lang(lng_create_group_crop); } else if (peer->isChannel()) { return lang(lng_create_channel_crop); } else { return lang(lng_settings_crop_profile); } } template QPixmap CreateSquarePixmap(int width, Callback &&paintCallback) { auto size = QSize(width, width) * cIntRetinaFactor(); auto image = QImage(size, QImage::Format_ARGB32_Premultiplied); image.setDevicePixelRatio(cRetinaFactor()); image.fill(Qt::transparent); { Painter p(&image); paintCallback(p); } return App::pixmapFromImageInPlace(std::move(image)); }; template void SuggestPhoto( const QImage &image, const QString &title, Callback &&callback) { auto badAspect = [](int a, int b) { return (a >= 10 * b); }; if (image.isNull() || badAspect(image.width(), image.height()) || badAspect(image.height(), image.width())) { Ui::show( Box(lang(lng_bad_photo)), LayerOption::KeepOther); return; } const auto box = Ui::show( Box(image, title), LayerOption::KeepOther); box->ready( ) | rpl::start_with_next( std::forward(callback), box->lifetime()); } template void SuggestPhotoFile( const FileDialog::OpenResult &result, const QString &title, Callback &&callback) { if (result.paths.isEmpty() && result.remoteContent.isEmpty()) { return; } auto image = [&] { if (!result.remoteContent.isEmpty()) { return App::readImage(result.remoteContent); } else if (!result.paths.isEmpty()) { return App::readImage(result.paths.front()); } return QImage(); }(); SuggestPhoto( image, title, std::forward(callback)); } template void ShowChoosePhotoBox( QPointer parent, const QString &title, Callback &&callback) { auto imgExtensions = cImgExtensions(); auto filter = qsl("Image files (*") + imgExtensions.join(qsl(" *")) + qsl(");;") + FileDialog::AllFilesFilter(); auto handleChosenPhoto = [ title, callback = std::forward(callback) ](auto &&result) mutable { SuggestPhotoFile(result, title, std::move(callback)); }; FileDialog::GetOpenPath( parent, lang(lng_choose_image), filter, std::move(handleChosenPhoto)); } } // namespace HistoryDownButton::HistoryDownButton(QWidget *parent, const style::TwoIconButton &st) : RippleButton(parent, st.ripple) , _st(st) { resize(_st.width, _st.height); setCursor(style::cur_pointer); hide(); } QImage HistoryDownButton::prepareRippleMask() const { return Ui::RippleAnimation::ellipseMask(QSize(_st.rippleAreaSize, _st.rippleAreaSize)); } QPoint HistoryDownButton::prepareRippleStartPosition() const { return mapFromGlobal(QCursor::pos()) - _st.rippleAreaPosition; } void HistoryDownButton::paintEvent(QPaintEvent *e) { Painter p(this); const auto ms = getms(); const auto over = isOver(); const auto down = isDown(); ((over || down) ? _st.iconBelowOver : _st.iconBelow).paint(p, _st.iconPosition, width()); paintRipple(p, _st.rippleAreaPosition.x(), _st.rippleAreaPosition.y(), ms); ((over || down) ? _st.iconAboveOver : _st.iconAbove).paint(p, _st.iconPosition, width()); if (_unreadCount > 0) { auto unreadString = QString::number(_unreadCount); if (unreadString.size() > 4) { unreadString = qsl("..") + unreadString.mid(unreadString.size() - 4); } Dialogs::Layout::UnreadBadgeStyle st; st.align = style::al_center; st.font = st::historyToDownBadgeFont; st.size = st::historyToDownBadgeSize; st.sizeId = Dialogs::Layout::UnreadBadgeInHistoryToDown; Dialogs::Layout::paintUnreadCount(p, unreadString, width(), 0, st, nullptr); } } void HistoryDownButton::setUnreadCount(int unreadCount) { if (_unreadCount != unreadCount) { _unreadCount = unreadCount; update(); } } EmojiButton::EmojiButton(QWidget *parent, const style::IconButton &st) : RippleButton(parent, st.ripple) , _st(st) { resize(_st.width, _st.height); setCursor(style::cur_pointer); } void EmojiButton::paintEvent(QPaintEvent *e) { Painter p(this); auto ms = getms(); p.fillRect(e->rect(), st::historyComposeAreaBg); paintRipple(p, _st.rippleAreaPosition.x(), _st.rippleAreaPosition.y(), ms, _rippleOverride ? &(*_rippleOverride)->c : nullptr); const auto over = isOver(); const auto loadingState = _loading ? _loading->computeState() : Ui::RadialState{ 0., 0, FullArcLength }; if (loadingState.shown < 1.) { p.setOpacity(1. - loadingState.shown); const auto icon = _iconOverride ? _iconOverride : &(over ? _st.iconOver : _st.icon); auto position = _st.iconPosition; if (position.x() < 0) { position.setX((width() - icon->width()) / 2); } if (position.y() < 0) { position.setY((height() - icon->height()) / 2); } icon->paint(p, position, width()); p.setOpacity(1.); } QRect inner(QPoint((width() - st::historyEmojiCircle.width()) / 2, (height() - st::historyEmojiCircle.height()) / 2), st::historyEmojiCircle); const auto color = (_colorOverride ? *_colorOverride : (over ? st::historyEmojiCircleFgOver : st::historyEmojiCircleFg)); if (anim::Disabled() && _loading && _loading->animating()) { anim::DrawStaticLoading( p, inner, st::historyEmojiCircleLine, color); } else { auto pen = color->p; pen.setWidth(st::historyEmojiCircleLine); pen.setCapStyle(Qt::RoundCap); p.setPen(pen); p.setBrush(Qt::NoBrush); PainterHighQualityEnabler hq(p); if (loadingState.arcLength < FullArcLength) { p.drawArc(inner, loadingState.arcFrom, loadingState.arcLength); } else { p.drawEllipse(inner); } } } void EmojiButton::step_loading(TimeMs ms, bool timer) { if (timer && !anim::Disabled()) { update(); } } void EmojiButton::setLoading(bool loading) { if (loading && !_loading) { _loading = std::make_unique( animation(this, &EmojiButton::step_loading), st::defaultInfiniteRadialAnimation); } if (loading) { _loading->start(); update(); } else if (_loading) { _loading->stop(); update(); } } void EmojiButton::setColorOverrides(const style::icon *iconOverride, const style::color *colorOverride, const style::color *rippleOverride) { _iconOverride = iconOverride; _colorOverride = colorOverride; _rippleOverride = rippleOverride; update(); } void EmojiButton::onStateChanged(State was, StateChangeSource source) { RippleButton::onStateChanged(was, source); auto wasOver = static_cast(was & StateFlag::Over); if (isOver() != wasOver) { update(); } } QPoint EmojiButton::prepareRippleStartPosition() const { if (!_st.rippleAreaSize) { return DisabledRippleStartPosition(); } return mapFromGlobal(QCursor::pos()) - _st.rippleAreaPosition; } QImage EmojiButton::prepareRippleMask() const { return RippleAnimation::ellipseMask(QSize(_st.rippleAreaSize, _st.rippleAreaSize)); } SendButton::SendButton(QWidget *parent) : RippleButton(parent, st::historyReplyCancel.ripple) { resize(st::historySendSize); } void SendButton::setType(Type type) { if (_type != type) { _contentFrom = grabContent(); _type = type; _a_typeChanged.finish(); _contentTo = grabContent(); _a_typeChanged.start([this] { update(); }, 0., 1., st::historyRecordVoiceDuration); update(); } if (_type != Type::Record) { _recordActive = false; _a_recordActive.finish(); } } void SendButton::setRecordActive(bool recordActive) { if (_recordActive != recordActive) { _recordActive = recordActive; _a_recordActive.start([this] { recordAnimationCallback(); }, _recordActive ? 0. : 1., _recordActive ? 1. : 0, st::historyRecordVoiceDuration); update(); } } void SendButton::finishAnimating() { _a_typeChanged.finish(); _a_recordActive.finish(); update(); } void SendButton::mouseMoveEvent(QMouseEvent *e) { AbstractButton::mouseMoveEvent(e); if (_recording) { if (_recordUpdateCallback) { _recordUpdateCallback(e->globalPos()); } } } void SendButton::paintEvent(QPaintEvent *e) { Painter p(this); auto ms = getms(); auto over = (isDown() || isOver()); auto changed = _a_typeChanged.current(ms, 1.); if (changed < 1.) { PainterHighQualityEnabler hq(p); p.setOpacity(1. - changed); auto targetRect = QRect((1 - kWideScale) / 2 * width(), (1 - kWideScale) / 2 * height(), kWideScale * width(), kWideScale * height()); auto hiddenWidth = anim::interpolate(0, (1 - kWideScale) / 2 * width(), changed); auto hiddenHeight = anim::interpolate(0, (1 - kWideScale) / 2 * height(), changed); p.drawPixmap(targetRect.marginsAdded(QMargins(hiddenWidth, hiddenHeight, hiddenWidth, hiddenHeight)), _contentFrom); p.setOpacity(changed); auto shownWidth = anim::interpolate((1 - kWideScale) / 2 * width(), 0, changed); auto shownHeight = anim::interpolate((1 - kWideScale) / 2 * height(), 0, changed); p.drawPixmap(targetRect.marginsAdded(QMargins(shownWidth, shownHeight, shownWidth, shownHeight)), _contentTo); } else if (_type == Type::Record) { auto recordActive = recordActiveRatio(); auto rippleColor = anim::color(st::historyAttachEmoji.ripple.color, st::historyRecordVoiceRippleBgActive, recordActive); paintRipple(p, (width() - st::historyAttachEmoji.rippleAreaSize) / 2, st::historyAttachEmoji.rippleAreaPosition.y(), ms, &rippleColor); auto fastIcon = [&] { if (recordActive == 1.) { return &st::historyRecordVoiceActive; } else if (over) { return &st::historyRecordVoiceOver; } return &st::historyRecordVoice; }; fastIcon()->paintInCenter(p, rect()); if (recordActive > 0. && recordActive < 1.) { p.setOpacity(recordActive); st::historyRecordVoiceActive.paintInCenter(p, rect()); p.setOpacity(1.); } } else if (_type == Type::Save) { auto &saveIcon = over ? st::historyEditSaveIconOver : st::historyEditSaveIcon; saveIcon.paint(p, st::historySendIconPosition, width()); } else if (_type == Type::Cancel) { paintRipple(p, (width() - st::historyAttachEmoji.rippleAreaSize) / 2, st::historyAttachEmoji.rippleAreaPosition.y(), ms); auto &cancelIcon = over ? st::historyReplyCancelIconOver : st::historyReplyCancelIcon; cancelIcon.paintInCenter(p, rect()); } else { auto &sendIcon = over ? st::historySendIconOver : st::historySendIcon; sendIcon.paint(p, st::historySendIconPosition, width()); } } void SendButton::onStateChanged(State was, StateChangeSource source) { RippleButton::onStateChanged(was, source); auto down = (state() & StateFlag::Down); if ((was & StateFlag::Down) != down) { if (down) { if (_type == Type::Record) { _recording = true; if (_recordStartCallback) { _recordStartCallback(); } } } else if (_recording) { _recording = false; if (_recordStopCallback) { _recordStopCallback(_recordActive); } } } } QPixmap SendButton::grabContent() { auto result = QImage(kWideScale * size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); result.setDevicePixelRatio(cRetinaFactor()); result.fill(Qt::transparent); { Painter p(&result); p.drawPixmap( (kWideScale - 1) / 2 * width(), (kWideScale - 1) / 2 * height(), GrabWidget(this)); } return App::pixmapFromImageInPlace(std::move(result)); } QImage SendButton::prepareRippleMask() const { auto size = (_type == Type::Record) ? st::historyAttachEmoji.rippleAreaSize : st::historyReplyCancel.rippleAreaSize; return Ui::RippleAnimation::ellipseMask(QSize(size, size)); } QPoint SendButton::prepareRippleStartPosition() const { auto real = mapFromGlobal(QCursor::pos()); auto size = (_type == Type::Record) ? st::historyAttachEmoji.rippleAreaSize : st::historyReplyCancel.rippleAreaSize; auto y = (_type == Type::Record) ? st::historyAttachEmoji.rippleAreaPosition.y() : (height() - st::historyReplyCancel.rippleAreaSize) / 2; return real - QPoint((width() - size) / 2, y); } void SendButton::recordAnimationCallback() { update(); if (_recordAnimationCallback) { _recordAnimationCallback(); } } UserpicButton::UserpicButton( QWidget *parent, const QString &cropTitle, Role role, const style::UserpicButton &st) : RippleButton(parent, st.changeButton.ripple) , _st(st) , _cropTitle(cropTitle) , _role(role) { Expects(_role == Role::ChangePhoto); _waiting = false; prepare(); } UserpicButton::UserpicButton( QWidget *parent, not_null controller, not_null peer, Role role, const style::UserpicButton &st) : RippleButton(parent, st.changeButton.ripple) , _st(st) , _controller(controller) , _peer(peer) , _cropTitle(CropTitle(_peer)) , _role(role) { processPeerPhoto(); prepare(); setupPeerViewers(); } UserpicButton::UserpicButton( QWidget *parent, not_null peer, Role role, const style::UserpicButton &st) : RippleButton(parent, st.changeButton.ripple) , _st(st) , _peer(peer) , _cropTitle(CropTitle(_peer)) , _role(role) { Expects(_role != Role::OpenProfile); _waiting = false; prepare(); } void UserpicButton::prepare() { resize(_st.size); _notShownYet = _waiting; if (!_waiting) { prepareUserpicPixmap(); } setClickHandlerByRole(); } void UserpicButton::setClickHandlerByRole() { switch (_role) { case Role::ChangePhoto: addClickHandler(App::LambdaDelayed( _st.changeButton.ripple.hideDuration, this, [this] { changePhotoLazy(); })); break; case Role::OpenPhoto: addClickHandler([this] { openPeerPhoto(); }); break; case Role::OpenProfile: addClickHandler([this] { Expects(_controller != nullptr); _controller->showPeerInfo(_peer); }); break; } } void UserpicButton::changePhotoLazy() { auto callback = crl::guard( this, [this](QImage &&image) { setImage(std::move(image)); }); ShowChoosePhotoBox(this, _cropTitle, std::move(callback)); } void UserpicButton::uploadNewPeerPhoto() { auto callback = crl::guard(this, [=](QImage &&image) { Auth().api().uploadPeerPhoto(_peer, std::move(image)); }); ShowChoosePhotoBox(this, _cropTitle, std::move(callback)); } void UserpicButton::openPeerPhoto() { Expects(_peer != nullptr); Expects(_controller != nullptr); if (_changeOverlayEnabled && _cursorInChangeOverlay) { uploadNewPeerPhoto(); return; } const auto id = _peer->userpicPhotoId(); if (!id) { return; } const auto photo = Auth().data().photo(id); if (photo->date) { Core::App().showPhoto(photo, _peer); } } void UserpicButton::setupPeerViewers() { Notify::PeerUpdateViewer( _peer, Notify::PeerUpdate::Flag::PhotoChanged ) | rpl::start_with_next([this] { processNewPeerPhoto(); update(); }, lifetime()); base::ObservableViewer( Auth().downloaderTaskFinished() ) | rpl::start_with_next([this] { if (_waiting && _peer->userpicLoaded()) { _waiting = false; startNewPhotoShowing(); } }, lifetime()); } void UserpicButton::paintEvent(QPaintEvent *e) { Painter p(this); if (!_waiting && _notShownYet) { _notShownYet = false; startAnimation(); } auto photoPosition = countPhotoPosition(); auto photoLeft = photoPosition.x(); auto photoTop = photoPosition.y(); auto ms = getms(); if (showSavedMessages()) { Ui::EmptyUserpic::PaintSavedMessages( p, photoPosition.x(), photoPosition.y(), width(), _st.photoSize); } else { if (_a_appearance.animating(ms)) { p.drawPixmapLeft(photoPosition, width(), _oldUserpic); p.setOpacity(_a_appearance.current()); } p.drawPixmapLeft(photoPosition, width(), _userpic); } if (_role == Role::ChangePhoto) { auto over = isOver() || isDown(); if (over) { PainterHighQualityEnabler hq(p); p.setPen(Qt::NoPen); p.setBrush(_userpicHasImage ? st::msgDateImgBg : _st.changeButton.textBgOver); p.drawEllipse( photoLeft, photoTop, _st.photoSize, _st.photoSize); } paintRipple( p, photoLeft, photoTop, ms, _userpicHasImage ? &st::shadowFg->c : &_st.changeButton.ripple.color->c); if (over || !_userpicHasImage) { auto iconLeft = (_st.changeIconPosition.x() < 0) ? (_st.photoSize - _st.changeIcon.width()) / 2 : _st.changeIconPosition.x(); auto iconTop = (_st.changeIconPosition.y() < 0) ? (_st.photoSize - _st.changeIcon.height()) / 2 : _st.changeIconPosition.y(); _st.changeIcon.paint( p, photoLeft + iconLeft, photoTop + iconTop, width()); } } else if (_changeOverlayEnabled) { auto current = _changeOverlayShown.current( ms, (isOver() || isDown()) ? 1. : 0.); auto barHeight = anim::interpolate( 0, _st.uploadHeight, current); if (barHeight > 0) { auto barLeft = photoLeft; auto barTop = photoTop + _st.photoSize - barHeight; auto rect = QRect( barLeft, barTop, _st.photoSize, barHeight); p.setClipRect(rect); { PainterHighQualityEnabler hq(p); p.setPen(Qt::NoPen); p.setBrush(_st.uploadBg); p.drawEllipse( photoLeft, photoTop, _st.photoSize, _st.photoSize); } auto iconLeft = (_st.uploadIconPosition.x() < 0) ? (_st.photoSize - _st.uploadIcon.width()) / 2 : _st.uploadIconPosition.x(); auto iconTop = (_st.uploadIconPosition.y() < 0) ? (_st.uploadHeight - _st.uploadIcon.height()) / 2 : _st.uploadIconPosition.y(); if (iconTop < barHeight) { _st.uploadIcon.paint( p, barLeft + iconLeft, barTop + iconTop, width()); } } } } QPoint UserpicButton::countPhotoPosition() const { auto photoLeft = (_st.photoPosition.x() < 0) ? (width() - _st.photoSize) / 2 : _st.photoPosition.x(); auto photoTop = (_st.photoPosition.y() < 0) ? (height() - _st.photoSize) / 2 : _st.photoPosition.y(); return { photoLeft, photoTop }; } QImage UserpicButton::prepareRippleMask() const { return Ui::RippleAnimation::ellipseMask(QSize( _st.photoSize, _st.photoSize)); } QPoint UserpicButton::prepareRippleStartPosition() const { return (_role == Role::ChangePhoto) ? mapFromGlobal(QCursor::pos()) - countPhotoPosition() : DisabledRippleStartPosition(); } void UserpicButton::processPeerPhoto() { Expects(_peer != nullptr); _waiting = !_peer->userpicLoaded(); if (_waiting) { _peer->loadUserpic(true); } if (_role == Role::OpenPhoto) { if (_peer->userpicPhotoUnknown()) { _peer->updateFullForced(); } _canOpenPhoto = (_peer->userpicPhotoId() != 0); updateCursor(); } } void UserpicButton::updateCursor() { Expects(_role == Role::OpenPhoto); auto pointer = _canOpenPhoto || (_changeOverlayEnabled && _cursorInChangeOverlay); setPointerCursor(pointer); } void UserpicButton::mouseMoveEvent(QMouseEvent *e) { RippleButton::mouseMoveEvent(e); if (_role == Role::OpenPhoto) { updateCursorInChangeOverlay(e->pos()); } } void UserpicButton::updateCursorInChangeOverlay(QPoint localPos) { auto photoPosition = countPhotoPosition(); auto overlayRect = QRect( photoPosition.x(), photoPosition.y() + _st.photoSize - _st.uploadHeight, _st.photoSize, _st.uploadHeight); auto inOverlay = overlayRect.contains(localPos); setCursorInChangeOverlay(inOverlay); } void UserpicButton::leaveEventHook(QEvent *e) { if (_role == Role::OpenPhoto) { setCursorInChangeOverlay(false); } return RippleButton::leaveEventHook(e); } void UserpicButton::setCursorInChangeOverlay(bool inOverlay) { Expects(_role == Role::OpenPhoto); if (_cursorInChangeOverlay != inOverlay) { _cursorInChangeOverlay = inOverlay; updateCursor(); } } void UserpicButton::processNewPeerPhoto() { if (_userpicCustom) { return; } processPeerPhoto(); if (!_waiting) { grabOldUserpic(); startNewPhotoShowing(); } } void UserpicButton::grabOldUserpic() { auto photoRect = QRect( countPhotoPosition(), QSize(_st.photoSize, _st.photoSize) ); _oldUserpic = GrabWidget(this, photoRect); } void UserpicButton::startNewPhotoShowing() { auto oldUniqueKey = _userpicUniqueKey; prepareUserpicPixmap(); update(); if (_notShownYet) { return; } if (oldUniqueKey != _userpicUniqueKey || _a_appearance.animating()) { startAnimation(); } } void UserpicButton::startAnimation() { _a_appearance.finish(); _a_appearance.start([this] { update(); }, 0, 1, _st.duration); } void UserpicButton::switchChangePhotoOverlay(bool enabled) { Expects(_role == Role::OpenPhoto); if (_changeOverlayEnabled != enabled) { _changeOverlayEnabled = enabled; if (enabled) { if (isOver()) { startChangeOverlayAnimation(); } updateCursorInChangeOverlay( mapFromGlobal(QCursor::pos())); } else { _changeOverlayShown.finish(); update(); } } } void UserpicButton::showSavedMessagesOnSelf(bool enabled) { if (_showSavedMessagesOnSelf != enabled) { _showSavedMessagesOnSelf = enabled; update(); } } bool UserpicButton::showSavedMessages() const { return _showSavedMessagesOnSelf && _peer && _peer->isSelf(); } void UserpicButton::startChangeOverlayAnimation() { auto over = isOver() || isDown(); _changeOverlayShown.start( [this] { update(); }, over ? 0. : 1., over ? 1. : 0., st::slideWrapDuration); update(); } void UserpicButton::onStateChanged( State was, StateChangeSource source) { RippleButton::onStateChanged(was, source); if (_changeOverlayEnabled) { auto mask = (StateFlag::Over | StateFlag::Down); auto wasOver = (was & mask) != 0; auto nowOver = (state() & mask) != 0; if (wasOver != nowOver) { startChangeOverlayAnimation(); } } } void UserpicButton::setImage(QImage &&image) { grabOldUserpic(); auto size = QSize(_st.photoSize, _st.photoSize); auto small = image.scaled( size * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); Images::prepareCircle(small); _userpic = App::pixmapFromImageInPlace(std::move(small)); _userpic.setDevicePixelRatio(cRetinaFactor()); _userpicCustom = _userpicHasImage = true; _result = std::move(image); startNewPhotoShowing(); } void UserpicButton::prepareUserpicPixmap() { if (_userpicCustom) { return; } auto size = _st.photoSize; auto paintButton = [&](Painter &p, const style::color &color) { PainterHighQualityEnabler hq(p); p.setBrush(color); p.setPen(Qt::NoPen); p.drawEllipse(0, 0, size, size); }; _userpicHasImage = _peer ? (_peer->currentUserpic() || _role != Role::ChangePhoto) : false; _userpic = CreateSquarePixmap(size, [&](Painter &p) { if (_userpicHasImage) { _peer->paintUserpic(p, 0, 0, _st.photoSize); } else { paintButton(p, _st.changeButton.textBg); } }); _userpicUniqueKey = _userpicHasImage ? _peer->userpicUniqueKey() : StorageKey(); } FeedUserpicButton::FeedUserpicButton( QWidget *parent, not_null controller, not_null feed, const style::FeedUserpicButton &st) : AbstractButton(parent) , _st(st) , _controller(controller) , _feed(feed) { prepare(); } void FeedUserpicButton::prepare() { resize(_st.size); Auth().data().feedUpdated( ) | rpl::filter([=](const Data::FeedUpdate &update) { return (update.feed == _feed) && (update.flag == Data::FeedUpdateFlag::Channels); }) | rpl::start_with_next([=] { crl::on_main(this, [=] { checkParts(); }); }, lifetime()); refreshParts(); } void FeedUserpicButton::checkParts() { if (!partsAreValid()) { refreshParts(); } } bool FeedUserpicButton::partsAreValid() const { const auto &channels = _feed->channels(); const auto count = std::min(int(channels.size()), 4); if (count != _parts.size()) { return false; } for (auto i = 0; i != count; ++i) { if (channels[i]->peer != _parts[i].channel) { return false; } } return true; } void FeedUserpicButton::refreshParts() { const auto &channels = _feed->channels(); const auto count = std::min(int(channels.size()), 4); const auto createButton = [&](not_null channel) { auto result = base::make_unique_q( this, _controller, channel, Ui::UserpicButton::Role::Custom, _st.innerPart); result->setAttribute(Qt::WA_TransparentForMouseEvents); result->show(); return result; }; const auto position = countInnerPosition(); auto x = position.x(); auto y = position.y(); const auto delta = _st.innerSize - _st.innerPart.photoSize; _parts.clear(); for (auto i = 0; i != count; ++i) { const auto channel = channels[i]->peer->asChannel(); _parts.push_back({ channel, createButton(channel) }); _parts.back().button->moveToLeft(x, y); switch (i) { case 0: case 2: x += delta; break; case 1: x -= delta; y += delta; break; } } } QPoint FeedUserpicButton::countInnerPosition() const { auto innerLeft = (_st.innerPosition.x() < 0) ? (width() - _st.innerSize) / 2 : _st.innerPosition.x(); auto innerTop = (_st.innerPosition.y() < 0) ? (height() - _st.innerSize) / 2 : _st.innerPosition.y(); return { innerLeft, innerTop }; } SilentToggle::SilentToggle(QWidget *parent, not_null channel) : IconButton(parent, st::historySilentToggle) , _channel(channel) , _checked(Auth().data().notifySilentPosts(_channel)) { Expects(!Auth().data().notifySilentPostsUnknown(_channel)); if (_checked) { refreshIconOverrides(); } setMouseTracking(true); } void SilentToggle::mouseMoveEvent(QMouseEvent *e) { IconButton::mouseMoveEvent(e); if (rect().contains(e->pos())) { Ui::Tooltip::Show(1000, this); } else { Ui::Tooltip::Hide(); } } void SilentToggle::setChecked(bool checked) { if (_checked != checked) { _checked = checked; refreshIconOverrides(); } } void SilentToggle::refreshIconOverrides() { const auto iconOverride = _checked ? &st::historySilentToggleOn : nullptr; const auto iconOverOverride = _checked ? &st::historySilentToggleOnOver : nullptr; setIconOverride(iconOverride, iconOverOverride); } void SilentToggle::leaveEventHook(QEvent *e) { IconButton::leaveEventHook(e); Ui::Tooltip::Hide(); } void SilentToggle::mouseReleaseEvent(QMouseEvent *e) { setChecked(!_checked); IconButton::mouseReleaseEvent(e); Ui::Tooltip::Show(0, this); Auth().data().updateNotifySettings( _channel, std::nullopt, _checked); } QString SilentToggle::tooltipText() const { return lang(_checked ? lng_wont_be_notified : lng_will_be_notified); } QPoint SilentToggle::tooltipPos() const { return QCursor::pos(); } } // namespace Ui