From 48c7a4046cda5a419ead12ed4a0c87e05f2afaee Mon Sep 17 00:00:00 2001 From: AlexeyZavar Date: Fri, 6 Feb 2026 21:50:50 +0300 Subject: [PATCH] feat: semi-transparent deleted messages --- Telegram/SourceFiles/ayu/ayu_settings.cpp | 8 +++ Telegram/SourceFiles/ayu/ayu_settings.h | 4 ++ .../ayu/ui/settings/settings_ayu.cpp | 1 + .../ayu/ui/settings/settings_chats.cpp | 2 - Telegram/SourceFiles/history/history_item.cpp | 9 +++ Telegram/SourceFiles/history/history_item.h | 3 + .../history/view/history_view_element.cpp | 44 ++++++++++++ .../history/view/history_view_element.h | 7 ++ .../history/view/history_view_message.cpp | 10 +++ .../view/media/history_view_media_grouped.cpp | 71 ++++++++++++++++--- .../view/media/history_view_media_grouped.h | 2 + 11 files changed, 150 insertions(+), 11 deletions(-) diff --git a/Telegram/SourceFiles/ayu/ayu_settings.cpp b/Telegram/SourceFiles/ayu/ayu_settings.cpp index 2aae650452..35609bbb1d 100644 --- a/Telegram/SourceFiles/ayu/ayu_settings.cpp +++ b/Telegram/SourceFiles/ayu/ayu_settings.cpp @@ -389,6 +389,12 @@ void AyuSettings::setHideFromBlocked(bool val) { save(); } +void AyuSettings::setSemiTransparentDeletedMessages(bool val) { + if (_semiTransparentDeletedMessages.current() == val) return; + _semiTransparentDeletedMessages = val; + save(); +} + void AyuSettings::setDisableAds(bool val) { if (_disableAds.current() == val) return; _disableAds = val; @@ -808,6 +814,7 @@ void to_json(nlohmann::json &j, const AyuSettings &s) { {"filtersEnabled", s._filtersEnabled.current()}, {"filtersEnabledInChats", s._filtersEnabledInChats.current()}, {"hideFromBlocked", s._hideFromBlocked.current()}, + {"semiTransparentDeletedMessages", s._semiTransparentDeletedMessages.current()}, {"disableAds", s._disableAds.current()}, {"disableStories", s._disableStories.current()}, {"disableCustomBackgrounds", s._disableCustomBackgrounds.current()}, @@ -898,6 +905,7 @@ void from_json(const nlohmann::json &j, AyuSettings &s) { s._filtersEnabled = j.value("filtersEnabled", defaults._filtersEnabled.current()); s._filtersEnabledInChats = j.value("filtersEnabledInChats", defaults._filtersEnabledInChats.current()); s._hideFromBlocked = j.value("hideFromBlocked", defaults._hideFromBlocked.current()); + s._semiTransparentDeletedMessages = j.value("semiTransparentDeletedMessages", defaults._semiTransparentDeletedMessages.current()); s._disableAds = j.value("disableAds", defaults._disableAds.current()); s._disableStories = j.value("disableStories", defaults._disableStories.current()); s._disableCustomBackgrounds = j.value("disableCustomBackgrounds", defaults._disableCustomBackgrounds.current()); diff --git a/Telegram/SourceFiles/ayu/ayu_settings.h b/Telegram/SourceFiles/ayu/ayu_settings.h index 6983dbfda0..0fce288e54 100644 --- a/Telegram/SourceFiles/ayu/ayu_settings.h +++ b/Telegram/SourceFiles/ayu/ayu_settings.h @@ -167,6 +167,7 @@ public: [[nodiscard]] bool filtersEnabled() const { return _filtersEnabled.current(); } [[nodiscard]] bool filtersEnabledInChats() const { return _filtersEnabledInChats.current(); } [[nodiscard]] bool hideFromBlocked() const { return _hideFromBlocked.current(); } + [[nodiscard]] bool semiTransparentDeletedMessages() const { return _semiTransparentDeletedMessages.current(); } [[nodiscard]] bool disableAds() const { return _disableAds.current(); } [[nodiscard]] bool disableStories() const { return _disableStories.current(); } [[nodiscard]] bool disableCustomBackgrounds() const { return _disableCustomBackgrounds.current(); } @@ -241,6 +242,7 @@ public: void setFiltersEnabled(bool val); void setFiltersEnabledInChats(bool val); void setHideFromBlocked(bool val); + void setSemiTransparentDeletedMessages(bool val); void setDisableAds(bool val); void setDisableStories(bool val); void setDisableCustomBackgrounds(bool val); @@ -316,6 +318,7 @@ public: [[nodiscard]] rpl::producer filtersEnabledChanges() const { return _filtersEnabled.value(); } [[nodiscard]] rpl::producer filtersEnabledInChatsChanges() const { return _filtersEnabledInChats.value(); } [[nodiscard]] rpl::producer hideFromBlockedChanges() const { return _hideFromBlocked.value(); } + [[nodiscard]] rpl::producer semiTransparentDeletedMessagesChanges() const { return _semiTransparentDeletedMessages.value(); } [[nodiscard]] rpl::producer disableAdsChanges() const { return _disableAds.value(); } [[nodiscard]] rpl::producer disableStoriesChanges() const { return _disableStories.value(); } [[nodiscard]] rpl::producer disableCustomBackgroundsChanges() const { return _disableCustomBackgrounds.value(); } @@ -399,6 +402,7 @@ private: rpl::variable _filtersEnabled = false; rpl::variable _filtersEnabledInChats = false; rpl::variable _hideFromBlocked = false; + rpl::variable _semiTransparentDeletedMessages = false; rpl::variable _disableAds = true; rpl::variable _disableStories = false; rpl::variable _disableCustomBackgrounds = true; diff --git a/Telegram/SourceFiles/ayu/ui/settings/settings_ayu.cpp b/Telegram/SourceFiles/ayu/ui/settings/settings_ayu.cpp index ed67297403..5cf823991e 100644 --- a/Telegram/SourceFiles/ayu/ui/settings/settings_ayu.cpp +++ b/Telegram/SourceFiles/ayu/ui/settings/settings_ayu.cpp @@ -490,6 +490,7 @@ void SetupOther(not_null container) { AddSettingToggle(container, tr::ayu_LocalPremium(), &AyuSettings::localPremium, &AyuSettings::setLocalPremium); AddSettingToggle(container, tr::ayu_DisableAds(), &AyuSettings::disableAds, &AyuSettings::setDisableAds); + AddSettingToggle(container, tr::ayu_SemiTransparentDeletedMessages(), &AyuSettings::semiTransparentDeletedMessages, &AyuSettings::setSemiTransparentDeletedMessages); } rpl::producer AyuGhost::title() { diff --git a/Telegram/SourceFiles/ayu/ui/settings/settings_chats.cpp b/Telegram/SourceFiles/ayu/ui/settings/settings_chats.cpp index 476099da04..21706d6f00 100644 --- a/Telegram/SourceFiles/ayu/ui/settings/settings_chats.cpp +++ b/Telegram/SourceFiles/ayu/ui/settings/settings_chats.cpp @@ -40,8 +40,6 @@ AyuChats::AyuChats( } void SetupStickersAndEmojiSettings(not_null container) { - auto *settings = &AyuSettings::getInstance(); - AddSubsectionTitle(container, tr::lng_settings_stickers_emoji()/*rpl::single(QString("Stickers and Emoji"))*/); AddSettingToggle(container, tr::ayu_ShowOnlyAddedEmojisAndStickers(), &AyuSettings::showOnlyAddedEmojisAndStickers, &AyuSettings::setShowOnlyAddedEmojisAndStickers); diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 21cc387e2a..81e5dd8321 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -3500,6 +3500,7 @@ void HistoryItem::setPostAuthor(const QString &postAuthor) { void HistoryItem::setDeleted() { _deleted = true; + _deletedAnimated = true; if (isService()) { const auto &settings = AyuSettings::getInstance(); @@ -3514,6 +3515,14 @@ bool HistoryItem::isDeleted() const { return _deleted; } +bool HistoryItem::wasDeletedAnimated() const { + return _deletedAnimated; +} + +void HistoryItem::markDeletedAnimated() { + _deletedAnimated = false; +} + void HistoryItem::setAyuHint(const QString &hint) { try { auto msgsigned = Get(); diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index c760111fe5..c825e7aae4 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -437,6 +437,8 @@ public: void setPostAuthor(const QString &author); void setDeleted(); bool isDeleted() const; + [[nodiscard]] bool wasDeletedAnimated() const; + void markDeletedAnimated(); void applyTTL(TimeId destroyAt); void setAyuHint(const QString &hint); void setRealId(MsgId newId); @@ -739,6 +741,7 @@ private: crl::time _reactionsLastRefreshed = 0; bool _deleted = false; + bool _deletedAnimated = false; int _unsupportedTTL = 0; TimeId _date = 0; diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index 20be37a49b..15fe7d63b8 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -48,6 +48,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/round_rect.h" #include "data/components/sponsored_messages.h" #include "data/data_channel.h" +#include "data/data_groups.h" #include "data/data_saved_sublist.h" #include "data/data_session.h" #include "data/data_todo_list.h" @@ -1152,6 +1153,15 @@ Element::Element( AddComponents(FakeBotAboutTop::Bit()); } } + if (replacing && replacing->_deletedOpacityAnimation.animating()) { + _deletedOpacityAnimation = replacing->takeDeletedAnimation(); + } else if (data->isDeleted() && data->wasDeletedAnimated()) { + // grouped messages handle it per-item + if (!history()->owner().groups().find(data)) { + startDeletedAnimation(); + data->markDeletedAnimated(); + } + } } bool Element::embedReactionsInBubble() const { @@ -1284,6 +1294,40 @@ void Element::repaint(QRect r) const { history()->owner().requestViewRepaint(this, r); } +float64 Element::deletedOpacity() const { + const auto &settings = AyuSettings::getInstance(); + if (!settings.semiTransparentDeletedMessages()) { + return 1.; + } + if (_context == Context::AdminLog) { // render normally in "View Deleted" + return 1.; + } + if (_data->isDeleted()) { + if (const auto group = history()->owner().groups().find(_data)) { + // animation works weirdly on grouped messages, so only a fixed opacity here + const auto allDeleted = ranges::all_of( + group->items, + &HistoryItem::isDeleted); + return allDeleted ? 0.7 : 1.; + } + return _deletedOpacityAnimation.value(0.7); + } + return 1.; +} + +void Element::startDeletedAnimation() { + _deletedOpacityAnimation.start( + [=] { repaint(); }, + 1., + 0.7, + 500, + anim::easeOutCubic); +} + +Ui::Animations::Simple Element::takeDeletedAnimation() { + return std::move(_deletedOpacityAnimation); +} + void Element::paintHighlight( Painter &p, const PaintContext &context, diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index f252fe9a78..95e31d9b4a 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/runtime_composer.h" #include "base/flags.h" #include "base/weak_ptr.h" +#include "ui/effects/animations.h" #include "ui/userpic_view.h" class History; @@ -458,6 +459,10 @@ public: [[nodiscard]] bool isHiddenByGroup() const; [[nodiscard]] virtual bool isHidden() const; + [[nodiscard]] float64 deletedOpacity() const; + void startDeletedAnimation(); + [[nodiscard]] Ui::Animations::Simple takeDeletedAnimation(); + [[nodiscard]] bool isIsolatedEmoji() const { return (_flags & Flag::SpecialOnlyEmoji) && _text.isIsolatedEmoji(); @@ -765,6 +770,8 @@ private: mutable Flags _flags = Flag(0); Context _context = Context(); + mutable Ui::Animations::Simple _deletedOpacityAnimation; + }; [[nodiscard]] int FindViewY( diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 13fb9df7c4..0d6b2050bd 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -991,6 +991,12 @@ void Message::draw(Painter &p, const PaintContext &context) const { } } + const auto deletedFade = deletedOpacity(); + const auto savedOpacityForDeleted = p.opacity(); + if (deletedFade < 1.) { + p.setOpacity(savedOpacityForDeleted * deletedFade); + } + const auto roll = media ? media->bubbleRoll() : Media::BubbleRoll(); if (roll) { p.save(); @@ -1321,6 +1327,10 @@ void Message::draw(Painter &p, const PaintContext &context) const { p.restoreTextPalette(); + if (deletedFade < 1.) { + p.setOpacity(savedOpacityForDeleted); + } + if (context.highlightPathCache && !context.highlightPathCache->isEmpty()) { const auto alpha = int(0.25 diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp index 6b8a133955..e2902bfb5e 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp @@ -27,6 +27,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "layout/layout_selection.h" #include "styles/style_chat.h" +// AyuGram includes +#include "ayu/ayu_settings.h" + + namespace HistoryView { namespace { @@ -404,6 +408,26 @@ void GroupedMedia::draw(Painter &p, const PaintContext &context) const { const auto tagged = lookupSpoilerTagMedia(); auto fullRect = QRect(); const auto subpartHighlight = IsSubGroupSelection(highlight); + + auto allDeleted = true; + auto anyDeleted = false; + const auto &ayuSettings = AyuSettings::getInstance(); + const auto perItemOpacityEnabled = ayuSettings.semiTransparentDeletedMessages(); + if (perItemOpacityEnabled) { + for (const auto &part : _parts) { + if (part.item->isDeleted()) { + anyDeleted = true; + } else { + allDeleted = false; + } + } + } + const auto perItemDeletedOpacity = perItemOpacityEnabled + && anyDeleted; + const auto elementDeletedOpacity = perItemDeletedOpacity + ? _parent->deletedOpacity() + : 1.; + for (auto i = 0, count = int(_parts.size()); i != count; ++i) { const auto &part = _parts[i]; auto partContext = context.withSelection(fullSelection @@ -430,15 +454,44 @@ void GroupedMedia::draw(Painter &p, const PaintContext &context) const { if (!part.cache.isNull()) { wasCache = true; } - part.content->drawGrouped( - p, - partContext, - part.geometry.translated(0, groupPadding.top()), - part.sides, - applyRoundingSides(rounding, part.sides), - highlightOpacity, - &part.cacheKey, - &part.cache); + if (perItemDeletedOpacity && part.item->isDeleted()) { + if (part.item->wasDeletedAnimated() + && !part.deletedAnimation.animating()) { + part.deletedAnimation.start( + [parent = _parent] { parent->repaint(); }, + 1., + 0.7, + 500, + anim::easeOutCubic); + part.item->markDeletedAnimated(); + } + const auto itemOpacity = part.deletedAnimation.value(0.7); + const auto adjustedOpacity = (elementDeletedOpacity > 0.) + ? (itemOpacity / elementDeletedOpacity) + : 0.; + const auto savedOp = p.opacity(); + p.setOpacity(savedOp * adjustedOpacity); + part.content->drawGrouped( + p, + partContext, + part.geometry.translated(0, groupPadding.top()), + part.sides, + applyRoundingSides(rounding, part.sides), + highlightOpacity, + &part.cacheKey, + &part.cache); + p.setOpacity(savedOp); + } else { + part.content->drawGrouped( + p, + partContext, + part.geometry.translated(0, groupPadding.top()), + part.sides, + applyRoundingSides(rounding, part.sides), + highlightOpacity, + &part.cacheKey, + &part.cache); + } if (!part.cache.isNull()) { nowCache = true; } diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h index 24499a7769..6e5adebd05 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_media.h" #include "data/data_document.h" #include "data/data_photo.h" +#include "ui/effects/animations.h" namespace Data { class Media; @@ -124,6 +125,7 @@ private: QRect geometry; mutable uint64 cacheKey = 0; mutable QPixmap cache; + mutable Ui::Animations::Simple deletedAnimation; };