Start animating emoji in filter titles.

This commit is contained in:
John Preston 2024-12-24 13:59:18 +04:00
parent 6cfbccd955
commit d874829b06
16 changed files with 363 additions and 144 deletions

View file

@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/filters/edit_filter_links.h" // FilterChatStatusText
#include "core/application.h"
#include "core/core_settings.h"
#include "core/ui_integration.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_chat_filters.h"
@ -26,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/boxes/confirm_box.h"
#include "ui/controls/filter_link_header.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
#include "ui/widgets/buttons.h"
#include "ui/filter_icons.h"
#include "ui/vertical_list.h"
@ -105,7 +107,7 @@ private:
Unexpected("Ui::FilterLinkHeaderType in TitleText.");
}
[[nodiscard]] TextWithEntities AboutText( // todo filter emoji
[[nodiscard]] TextWithEntities AboutText(
Ui::FilterLinkHeaderType type,
TextWithEntities title) {
using Type = Ui::FilterLinkHeaderType;
@ -147,10 +149,17 @@ void InitFilterLinkHeader(
Ui::LookupFilterIconByEmoji(
iconEmoji
).value_or(Ui::FilterIcon::Custom)).active;
const auto makeContext = [=](Fn<void()> repaint) {
return Core::MarkedTextContext{
.session = &box->peerListUiShow()->session(),
.customEmojiRepaint = std::move(repaint),
};
};
auto header = Ui::MakeFilterLinkHeader(box, {
.type = type,
.title = TitleText(type)(tr::now),
.about = AboutText(type, title),
.makeAboutContext = makeContext,
.folderTitle = title,
.folderIcon = icon,
.badge = (type == Ui::FilterLinkHeaderType::AddingChats
@ -541,7 +550,7 @@ void ShowImportToast(
const auto phrase = created
? tr::lng_filters_added_title
: tr::lng_filters_updated_title;
auto text = Ui::Text::Wrapped( // todo filter emoji
auto text = Ui::Text::Wrapped(
phrase(tr::now, lt_folder, title, Ui::Text::WithEntities),
EntityType::Bold);
if (added > 0) {
@ -550,7 +559,16 @@ void ShowImportToast(
: tr::lng_filters_updated_also;
text.append('\n').append(phrase(tr::now, lt_count, added));
}
strong->showToast(std::move(text));
const auto makeContext = [=](not_null<QWidget*> widget) {
return Core::MarkedTextContext{
.session = &strong->session(),
.customEmojiRepaint = [=] { widget->update(); },
};
};
strong->showToast({
.text = std::move(text),
.textContext = makeContext,
});
}
void HandleEnterInBox(not_null<Ui::BoxContent*> box) {
@ -619,10 +637,17 @@ void ProcessFilterInvite(
raw->setRealContentHeight(box->heightValue());
const auto makeContext = [=](Fn<void()> update) {
return Core::MarkedTextContext{
.session = &strong->session(),
.customEmojiRepaint = update,
};
};
auto owned = Ui::FilterLinkProcessButton(
box,
type,
title,
makeContext,
std::move(badge));
const auto button = owned.data();
@ -842,10 +867,17 @@ void ProcessFilterRemove(
raw->adjust(min, max, addedTop);
}, type, title, iconEmoji, rpl::single(0), horizontalFilters);
const auto makeContext = [=](Fn<void()> update) {
return Core::MarkedTextContext{
.session = &strong->session(),
.customEmojiRepaint = update,
};
};
auto owned = Ui::FilterLinkProcessButton(
box,
type,
title,
makeContext,
std::move(badge));
const auto button = owned.data();

View file

@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/premium_limits_box.h"
#include "boxes/premium_preview_box.h"
#include "chat_helpers/emoji_suggestions_widget.h"
#include "chat_helpers/message_field.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "data/data_channel.h"
@ -394,28 +395,28 @@ void EditFilterBox(
tr::lng_filters_edit()));
box->setCloseByOutsideClick(false);
const auto session = &window->session();
Data::AmPremiumValue(
&window->session()
session
) | rpl::start_with_next([=] {
box->closeBox();
}, box->lifetime());
const auto content = box->verticalLayout();
const auto current = filter.title();
const auto name = content->add(
object_ptr<Ui::InputField>(
box,
st::windowFilterNameInput,
tr::lng_filters_new_name(),
filter.title().text), // todo filter emoji
Ui::InputField::Mode::SingleLine,
tr::lng_filters_new_name()),
st::markdownLinkFieldPadding);
InitMessageFieldHandlers(window, name, ChatHelpers::PauseReason::Layer);
name->setTextWithTags({
current.text,
TextUtilities::ConvertEntitiesToTextTags(current.entities),
}, Ui::InputField::HistoryAction::Clear);
name->setMaxLength(kMaxFilterTitleLength);
name->setInstantReplaces(Ui::InstantReplaces::Default());
name->setInstantReplacesEnabled(
Core::App().settings().replaceEmojiValue());
Ui::Emoji::SuggestionsController::Init(
box->getDelegate()->outerContainer(),
name,
&window->session());
const auto nameEditing = box->lifetime().make_state<NameEditing>(
NameEditing{ name });
@ -672,8 +673,11 @@ void EditFilterBox(
}
const auto collect = [=]() -> std::optional<Data::ChatFilter> {
// todo filter emoji
const auto title = TextWithEntities{ name->getLastText().trimmed() };
const auto entered = name->getTextWithTags();
const auto title = TextWithEntities{
entered.text,
TextUtilities::ConvertTextTagsToEntities(entered.tags),
};
const auto rules = data->current();
if (title.empty()) {
name->showError();

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/filters/edit_filter_chats_list.h"
#include "core/ui_integration.h"
#include "data/data_chat_filters.h"
#include "data/data_premium_limits.h"
#include "data/data_session.h"
@ -63,13 +64,27 @@ private:
class ExceptionRow final : public ChatsListBoxController::Row {
public:
explicit ExceptionRow(not_null<History*> history);
ExceptionRow(
not_null<History*> history,
not_null<PeerListDelegate*> delegate);
QString generateName() override;
QString generateShortName() override;
PaintRoundImageCallback generatePaintUserpicCallback(
bool forceRound) override;
void paintStatusText(
Painter &p,
const style::PeerListItem &st,
int x,
int y,
int availableWidth,
int outerWidth,
bool selected) override;
private:
Ui::Text::String _filtersText;
};
class TypeController final : public PeerListController {
@ -126,15 +141,29 @@ Flag TypeRow::flag() const {
return static_cast<Flag>(id() & 0xFFFF);
}
ExceptionRow::ExceptionRow(not_null<History*> history) : Row(history) {
auto filters = QStringList();
ExceptionRow::ExceptionRow(
not_null<History*> history,
not_null<PeerListDelegate*> delegate)
: Row(history) {
auto filters = TextWithEntities();
for (const auto &filter : history->owner().chatsFilters().list()) {
if (filter.contains(history) && filter.id()) {
filters << filter.title().text; // todo filter emoji
if (!filters.empty()) {
filters.append(u", "_q);
}
filters.append(filter.title());
}
}
if (!filters.isEmpty()) {
setCustomStatus(filters.join(", "));
if (!filters.empty()) {
const auto repaint = [=] { delegate->peerListUpdateRow(this); };
_filtersText.setMarkedText(
st::defaultTextStyle,
filters,
kMarkupTextOptions,
Core::MarkedTextContext{
.session = &history->session(),
.customEmojiRepaint = repaint,
});
} else if (peer()->isSelf()) {
setCustomStatus(tr::lng_saved_forward_here(tr::now));
}
@ -176,6 +205,37 @@ PaintRoundImageCallback ExceptionRow::generatePaintUserpicCallback(
};
}
void ExceptionRow::paintStatusText(
Painter &p,
const style::PeerListItem &st,
int x,
int y,
int availableWidth,
int outerWidth,
bool selected) {
if (_filtersText.isEmpty()) {
Row::paintStatusText(
p,
st,
x,
y,
availableWidth,
outerWidth,
selected);
} else {
p.setPen(selected ? st.statusFgOver : st.statusFg);
_filtersText.draw(p, {
.position = { x, y },
.outerWidth = outerWidth,
.availableWidth = availableWidth,
.palette = &st::defaultTextPalette,
.now = crl::now(),
.pausedEmoji = false,
.elisionLines = 1,
});
}
}
TypeController::TypeController(
not_null<Main::Session*> session,
Flags options,
@ -418,7 +478,7 @@ void EditFilterChatsListController::prepareViewHook() {
const auto rows = std::make_unique<std::optional<ExceptionRow>[]>(count);
auto i = 0;
for (const auto &history : _peers) {
rows[i++].emplace(history);
rows[i++].emplace(history, delegate());
}
auto pointers = std::vector<ExceptionRow*>();
pointers.reserve(count);
@ -499,7 +559,7 @@ auto EditFilterChatsListController::createRow(not_null<History*> history)
return nullptr;
}
return history->inChatList()
? std::make_unique<ExceptionRow>(history)
? std::make_unique<ExceptionRow>(history, delegate())
: nullptr;
}

View file

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peers/edit_peer_invite_link.h" // InviteLinkQrBox.
#include "boxes/peer_list_box.h"
#include "boxes/premium_limits_box.h"
#include "core/ui_integration.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_chat_filters.h"
@ -535,6 +536,12 @@ void LinkController::addHeader(not_null<Ui::VerticalLayout*> container) {
}, verticalLayout->lifetime());
verticalLayout->add(std::move(icon.widget));
const auto makeContext = [=](Fn<void()> update) {
return Core::MarkedTextContext{
.session = &_window->session(),
.customEmojiRepaint = update,
};
};
verticalLayout->add(
object_ptr<Ui::CenterWrap<>>(
verticalLayout,
@ -543,12 +550,14 @@ void LinkController::addHeader(not_null<Ui::VerticalLayout*> container) {
(_data.url.isEmpty()
? tr::lng_filters_link_no_about(Ui::Text::WithEntities)
: tr::lng_filters_link_share_about(
lt_folder, // todo filter emoji
lt_folder,
rpl::single(Ui::Text::Wrapped(
_filterTitle,
EntityType::Bold)),
Ui::Text::WithEntities)),
st::settingsFilterDividerLabel)),
st::settingsFilterDividerLabel,
st::defaultPopupMenu,
makeContext)),
st::filterLinkDividerLabelPadding);
verticalLayout->geometryValue(

View file

@ -356,7 +356,8 @@ void ShareBox::prepare() {
[this](FilterId id) {
_inner->applyChatFilter(id);
scrollToY(0);
});
},
Window::GifPauseReason::Layer);
chatsFilters->lower();
chatsFilters->heightValue() | rpl::start_with_next([this](int h) {
updateScrollSkips();

View file

@ -1345,6 +1345,7 @@ void Widget::toggleFiltersMenu(bool enabled) {
controller()->setActiveChatsFilter(id);
}
},
Window::GifPauseReason::Any,
controller(),
true);
raw->show();

View file

@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/premium_limits_box.h"
#include "boxes/premium_preview_box.h"
#include "core/application.h"
#include "core/ui_integration.h"
#include "data/data_chat_filters.h"
#include "data/data_folder.h"
#include "data/data_peer.h"
@ -57,14 +58,13 @@ public:
FilterRowButton(
not_null<QWidget*> parent,
not_null<Main::Session*> session,
const Data::ChatFilter &filter);
FilterRowButton(
not_null<QWidget*> parent,
const Data::ChatFilter &filter,
const QString &description);
const QString &description = {});
void setRemoved(bool removed);
void updateData(const Data::ChatFilter &filter);
void updateData(
const Data::ChatFilter &filter,
bool ignoreCount = false);
void updateCount(const Data::ChatFilter &filter);
[[nodiscard]] rpl::producer<> removeRequests() const;
@ -80,20 +80,13 @@ private:
Normal,
};
FilterRowButton(
not_null<QWidget*> parent,
Main::Session *session,
const Data::ChatFilter &filter,
const QString &description,
State state);
void paintEvent(QPaintEvent *e) override;
void setup(const Data::ChatFilter &filter, const QString &status);
void setState(State state, bool force = false);
void updateButtonsVisibility();
Main::Session *_session = nullptr;
const not_null<Main::Session*> _session;
Ui::IconButton _remove;
Ui::RoundButton _restore;
@ -177,50 +170,43 @@ struct FilterRow {
FilterRowButton::FilterRowButton(
not_null<QWidget*> parent,
not_null<Main::Session*> session,
const Data::ChatFilter &filter)
: FilterRowButton(
parent,
session,
filter,
ComputeCountString(session, filter),
State::Normal) {
}
FilterRowButton::FilterRowButton(
not_null<QWidget*> parent,
const Data::ChatFilter &filter,
const QString &description)
: FilterRowButton(parent, nullptr, filter, description, State::Suggested) {
}
FilterRowButton::FilterRowButton(
not_null<QWidget*> parent,
Main::Session *session,
const Data::ChatFilter &filter,
const QString &status,
State state)
: RippleButton(parent, st::defaultRippleAnimation)
, _session(session)
, _remove(this, st::filtersRemove)
, _restore(this, tr::lng_filters_restore(), st::stickersUndoRemove)
, _add(this, tr::lng_filters_recommended_add(), st::stickersTrendingAdd)
, _state(state) {
, _state(description.isEmpty() ? State::Normal : State::Suggested) {
_restore.setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
_add.setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
setup(filter, status);
setup(filter, description.isEmpty()
? ComputeCountString(session, filter)
: description);
}
void FilterRowButton::setRemoved(bool removed) {
setState(removed ? State::Removed : State::Normal);
}
void FilterRowButton::updateData(const Data::ChatFilter &filter) {
void FilterRowButton::updateData(
const Data::ChatFilter &filter,
bool ignoreCount) {
Expects(_session != nullptr);
// todo filter emoji
_title.setText(st::contactsNameStyle, filter.title().text);
_title.setMarkedText(
st::contactsNameStyle,
filter.title(),
kMarkupTextOptions,
Core::MarkedTextContext{
.session = _session,
.customEmojiRepaint = [=] { update(); },
});
_icon = Ui::ComputeFilterIcon(filter);
_colorIndex = filter.colorIndex();
updateCount(filter);
if (!ignoreCount) {
updateCount(filter);
}
}
void FilterRowButton::updateCount(const Data::ChatFilter &filter) {
@ -243,12 +229,9 @@ void FilterRowButton::setup(
const Data::ChatFilter &filter,
const QString &status) {
resize(width(), st::defaultPeerListItem.height);
// todo filter emoji
_title.setText(st::contactsNameStyle, filter.title().text);
_status = status;
_icon = Ui::ComputeFilterIcon(filter);
_colorIndex = filter.colorIndex();
_status = status;
updateData(filter, true);
setState(_state, true);
sizeValue() | rpl::start_with_next([=](QSize size) {
@ -648,6 +631,7 @@ void FilterRowButton::paintEvent(QPaintEvent *e) {
state->suggested = state->suggested.current() + 1;
const auto button = aboutRows->add(object_ptr<FilterRowButton>(
aboutRows,
session,
filter,
suggestion.description));
button->addRequests(

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "ui/image/image_prepare.h"
#include "ui/text/text_utilities.h"
#include "ui/painter.h"
#include "ui/rp_widget.h"
#include "ui/ui_utility.h"
@ -28,6 +29,11 @@ namespace {
constexpr auto kBodyAnimationPart = 0.90;
constexpr auto kTitleAdditionalScale = 0.05;
struct PreviewState {
Fn<QImage()> frame;
rpl::lifetime lifetime;
};
class Widget final : public RpWidget {
public:
Widget(
@ -61,7 +67,7 @@ private:
} _progress;
rpl::variable<int> _badge;
QImage _preview;
PreviewState _preview;
QRectF _previewRect;
QString _titleText;
@ -71,6 +77,7 @@ private:
QPainterPath _titlePath;
TextWithEntities _folderTitle;
Fn<std::any(Fn<void()>)> _makeContext;
not_null<const style::icon*> _folderIcon;
bool _horizontalFilters = false;
@ -80,55 +87,94 @@ private:
};
[[nodiscard]] QImage GeneratePreview(
[[nodiscard]] PreviewState GeneratePreview(
not_null<Ui::RpWidget*> parent,
const TextWithEntities &title,
Fn<std::any(Fn<void()>)> makeContext,
int badge) {
using Tabs = Ui::ChatsFiltersTabs;
auto owned = parent->lifetime().make_state<base::unique_qptr<Tabs>>(
base::make_unique_q<Tabs>(parent, st::dialogsSearchTabs));
const auto raw = owned->get();
auto preview = PreviewState();
struct State {
State(not_null<Ui::RpWidget*> parent)
: tabs(parent, st::dialogsSearchTabs) {
}
Tabs tabs;
QImage cache;
bool dirty = true;
};
const auto state = preview.lifetime.make_state<State>(parent);
preview.frame = [=] {
if (state->dirty) {
const auto raw = &state->tabs;
state->cache.fill(st::windowBg->c);
auto p = QPainter(&state->cache);
Ui::RenderWidget(p, raw, QPoint(), raw->rect());
const auto &r = st::defaultEmojiSuggestions.fadeRight;
const auto &l = st::defaultEmojiSuggestions.fadeLeft;
const auto padding = st::filterLinkSubsectionTitlePadding.top();
const auto w = raw->width();
const auto h = raw->height();
r.fill(p, QRect(w - r.width() - padding, 0, r.width(), h));
l.fill(p, QRect(padding, 0, l.width(), h));
p.fillRect(0, 0, padding, h, st::windowBg);
p.fillRect(w - padding, 0, padding, raw->height(), st::windowBg);
}
return state->cache;
};
const auto raw = &state->tabs;
const auto repaint = [=] {
state->dirty = true;
};
raw->setSections({
tr::lng_filters_name_people(tr::now),
title.text, // todo filter emoji
tr::lng_filters_name_unread(tr::now),
});
TextWithEntities{ tr::lng_filters_name_people(tr::now) },
title,
TextWithEntities{ tr::lng_filters_name_unread(tr::now) },
}, makeContext(repaint));
raw->fitWidthToSections();
raw->setActiveSectionFast(1);
raw->stopAnimation();
auto result = QImage(
auto &result = state->cache;
result = QImage(
raw->size() * style::DevicePixelRatio(),
QImage::Format_ARGB32_Premultiplied);
result.setDevicePixelRatio(style::DevicePixelRatio());
result.fill(st::windowBg->c);
{
auto p = QPainter(&result);
Ui::RenderWidget(p, raw, QPoint(), raw->rect());
raw->hide();
const auto &r = st::defaultEmojiSuggestions.fadeRight;
const auto &l = st::defaultEmojiSuggestions.fadeLeft;
const auto padding = st::filterLinkSubsectionTitlePadding.top();
const auto w = raw->width();
const auto h = raw->height();
r.fill(p, QRect(w - r.width() - padding, 0, r.width(), h));
l.fill(p, QRect(padding, 0, l.width(), h));
p.fillRect(0, 0, padding, h, st::windowBg);
p.fillRect(w - padding, 0, padding, raw->height(), st::windowBg);
}
owned->reset();
return result;
style::PaletteChanged(
) | rpl::start_with_next(repaint, preview.lifetime);
return preview;
}
[[nodiscard]] QImage GeneratePreview(
[[nodiscard]] PreviewState GeneratePreview(
const TextWithEntities &title,
Fn<std::any(Fn<void()>)> makeContext,
not_null<const style::icon*> icon,
int badge) {
auto preview = PreviewState();
struct State {
QImage bg;
QImage composed;
Ui::Text::String string;
bool dirty = true;
};
const auto state = preview.lifetime.make_state<State>();
const auto repaint = [=] {
state->dirty = true;
};
const auto size = st::filterLinkPreview;
const auto ratio = style::DevicePixelRatio();
const auto radius = st::filterLinkPreviewRadius;
const auto full = QSize(size, size) * ratio;
auto result = QImage(full, QImage::Format_ARGB32_Premultiplied);
auto &result = state->bg;
result = QImage(full, QImage::Format_ARGB32_Premultiplied);
result.setDevicePixelRatio(ratio);
result.fill(st::windowBg->c);
@ -149,16 +195,19 @@ private:
const auto myIconTop = st::filterLinkPreviewMyBottom - iconHeight;
icon->paint(p, iconLeft, myIconTop, size);
const auto paintName = [&](const QString &text, int top) {
auto string = Ui::Text::String(
st.style,
const auto fillName = [=](const TextWithEntities &text) {
state->string = Ui::Text::String(
st::windowFiltersButton.style,
text,
kDefaultTextOptions,
available);
string.draw(p, {
kMarkupTextOptions,
available,
makeContext(repaint));
};
const auto paintName = [=](QPainter &p, int top) {
state->string.draw(p, {
.position = QPoint(
std::max(
(column - string.maxWidth()) / 2,
(column - state->string.maxWidth()) / 2,
skip),
top),
.outerWidth = available,
@ -169,9 +218,9 @@ private:
};
p.setFont(st.style.font);
p.setPen(st.textFg);
paintName(tr::lng_filters_all(tr::now), st::filterLinkPreviewAllTop);
p.setPen(st.textFgActive);
paintName(title.text, st::filterLinkPreviewMyTop); // todo filter emoji
fillName({ tr::lng_filters_all(tr::now) });
paintName(p, st::filterLinkPreviewAllTop);
fillName(title);
auto hq = PainterHighQualityEnabler(p);
@ -226,7 +275,19 @@ private:
p.drawRoundedRect(0, 0, size, size, radius, radius);
p.end();
return Images::Round(std::move(result), Images::CornersMask(radius));
result = Images::Round(std::move(result), Images::CornersMask(radius));
preview.frame = [=] {
if (state->dirty) {
state->composed = state->bg;
auto p = QPainter(&state->composed);
p.setPen(st.textFgActive);
paintName(p, st::filterLinkPreviewMyTop);
}
return state->composed;
};
return preview;
}
Widget::Widget(
@ -236,7 +297,9 @@ Widget::Widget(
, _about(CreateChild<FlatLabel>(
this,
rpl::single(descriptor.about.value()),
st::filterLinkAbout))
st::filterLinkAbout,
st::defaultPopupMenu,
descriptor.makeAboutContext))
, _close(CreateChild<IconButton>(this, st::boxTitleClose))
, _aboutPadding(st::boxRowPadding)
, _badge(std::move(descriptor.badge))
@ -244,19 +307,15 @@ Widget::Widget(
, _titleFont(st::boxTitle.style.font)
, _titlePadding(st::filterLinkTitlePadding)
, _folderTitle(descriptor.folderTitle)
, _makeContext(descriptor.makeAboutContext)
, _folderIcon(descriptor.folderIcon)
, _horizontalFilters(descriptor.horizontalFilters) {
setMinimumHeight(st::boxTitleHeight);
refreshTitleText();
setTitlePosition(st::boxTitlePosition.x(), st::boxTitlePosition.y());
style::PaletteChanged(
) | rpl::start_with_next([this] {
_preview = QImage();
}, lifetime());
_badge.changes() | rpl::start_with_next([this] {
_preview = QImage();
_preview = PreviewState();
update();
}, lifetime());
}
@ -332,8 +391,9 @@ QRectF Widget::previewRect(
float64 topProgress,
float64 sizeProgress) const {
if (_horizontalFilters) {
const auto size = (_preview.size() / style::DevicePixelRatio())
* sizeProgress;
const auto size = (_preview.frame
? (_preview.frame().size() / style::DevicePixelRatio())
: QSize()) * sizeProgress;
return QRectF(
(width() - size.width()) / 2.,
st::filterLinkPreviewTop * 1.5 * topProgress,
@ -355,16 +415,27 @@ void Widget::paintEvent(QPaintEvent *e) {
p.setOpacity(_progress.body);
if (_progress.top) {
auto hq = PainterHighQualityEnabler(p);
if (_preview.isNull()) {
if (!_preview.frame) {
const auto badge = _badge.current();
const auto makeContext = [=](Fn<void()> repaint) {
return _makeContext([=] { repaint(); update(); });
};
if (_horizontalFilters) {
_preview = GeneratePreview(this, _folderTitle, badge);
_preview = GeneratePreview(
this,
_folderTitle,
makeContext,
badge);
Widget::resizeEvent(nullptr);
} else {
_preview = GeneratePreview(_folderTitle, _folderIcon, badge);
_preview = GeneratePreview(
_folderTitle,
makeContext,
_folderIcon,
badge);
}
}
p.drawImage(_previewRect, _preview);
p.drawImage(_previewRect, _preview.frame());
}
p.resetTransform();
@ -415,13 +486,14 @@ object_ptr<RoundButton> FilterLinkProcessButton(
not_null<QWidget*> parent,
FilterLinkHeaderType type,
TextWithEntities title,
Fn<std::any(Fn<void()>)> makeContext,
rpl::producer<int> badge) {
const auto st = &st::filterInviteBox.button;
const auto badgeSt = &st::filterInviteButtonBadgeStyle;
auto result = object_ptr<RoundButton>(parent, rpl::single(u""_q), *st);
struct Data {
QString text;
TextWithEntities text;
QString badge;
};
auto data = std::move(
@ -429,32 +501,41 @@ object_ptr<RoundButton> FilterLinkProcessButton(
) | rpl::map([=](int count) {
const auto badge = count ? QString::number(count) : QString();
const auto with = [&](QString badge) {
return rpl::map([=](QString text) {
return rpl::map([=](TextWithEntities text) {
return Data{ text, badge };
});
};
switch (type) {
case FilterLinkHeaderType::AddingFilter:
return badge.isEmpty()
? tr::lng_filters_by_link_add_no() | with(QString())
? tr::lng_filters_by_link_add_no(
Ui::Text::WithEntities
) | with(QString())
: tr::lng_filters_by_link_add_button(
lt_folder,
rpl::single(title.text) // todo filter emoji
rpl::single(title),
Ui::Text::WithEntities
) | with(badge);
case FilterLinkHeaderType::AddingChats:
return badge.isEmpty()
? tr::lng_filters_by_link_join_no() | with(QString())
? tr::lng_filters_by_link_join_no(
Ui::Text::WithEntities
) | with(QString())
: tr::lng_filters_by_link_and_join_button(
lt_count,
rpl::single(float64(count))) | with(badge);
rpl::single(float64(count)),
Ui::Text::WithEntities) | with(badge);
case FilterLinkHeaderType::AllAdded:
return tr::lng_box_ok() | with(QString());
return tr::lng_box_ok(Ui::Text::WithEntities) | with(QString());
case FilterLinkHeaderType::Removing:
return badge.isEmpty()
? tr::lng_filters_by_link_remove_button() | with(QString())
? tr::lng_filters_by_link_remove_button(
Ui::Text::WithEntities
) | with(QString())
: tr::lng_filters_by_link_and_quit_button(
lt_count,
rpl::single(float64(count))) | with(badge);
rpl::single(float64(count)),
Ui::Text::WithEntities) | with(badge);
}
Unexpected("Type in FilterLinkProcessButton.");
}) | rpl::flatten_latest();
@ -520,7 +601,11 @@ object_ptr<RoundButton> FilterLinkProcessButton(
}, label->lifetime());
std::move(data) | rpl::start_with_next([=](Data data) {
label->text.setText(st::filterInviteButtonStyle, data.text);
label->text.setMarkedText(
st::filterInviteButtonStyle,
data.text,
kMarkupTextOptions,
makeContext([=] { label->update(); }));
label->badge.setText(st::filterInviteButtonBadgeStyle, data.badge);
label->update();
}, label->lifetime());

View file

@ -26,6 +26,7 @@ struct FilterLinkHeaderDescriptor {
base::required<FilterLinkHeaderType> type;
base::required<QString> title;
base::required<TextWithEntities> about;
Fn<std::any(Fn<void()>)> makeAboutContext;
base::required<TextWithEntities> folderTitle;
not_null<const style::icon*> folderIcon;
rpl::producer<int> badge;
@ -46,6 +47,7 @@ struct FilterLinkHeader {
not_null<QWidget*> parent,
FilterLinkHeaderType type,
TextWithEntities title,
Fn<std::any(Fn<void()>)> makeContext,
rpl::producer<int> badge);
} // namespace Ui

View file

@ -46,22 +46,25 @@ ChatsFiltersTabs::ChatsFiltersTabs(
}
bool ChatsFiltersTabs::setSectionsAndCheckChanged(
std::vector<QString> &&sections) {
std::vector<TextWithEntities> &&sections,
const std::any &context,
Fn<bool()> paused) {
const auto &was = sectionsRef();
const auto changed = [&] {
if (was.size() != sections.size()) {
return true;
}
for (auto i = 0; i < sections.size(); i++) {
if (was[i].label.toString() != sections[i]) {
if (was[i].label.toTextWithEntities() != sections[i]) {
return true;
}
}
return false;
}();
if (changed) {
Ui::DiscreteSlider::setSections(std::move(sections));
Ui::DiscreteSlider::setSections(std::move(sections), context);
}
_emojiPaused = std::move(paused);
return changed;
}
@ -171,6 +174,7 @@ void ChatsFiltersTabs::paintEvent(QPaintEvent *e) {
const auto clip = e->rect();
const auto range = getCurrentActiveRange();
const auto activeIndex = activeSection();
const auto now = crl::now();
auto index = 0;
auto raisedIndex = -1;
@ -225,6 +229,8 @@ void ChatsFiltersTabs::paintEvent(QPaintEvent *e) {
.position = QPoint(labelLeft, _st.labelTop),
.outerWidth = width(),
.availableWidth = section.label.maxWidth(),
.now = now,
.pausedEmoji = _emojiPaused && _emojiPaused(),
});
{
const auto it = _unreadCounts.find(index);

View file

@ -27,7 +27,10 @@ public:
not_null<Ui::RpWidget*> parent,
const style::SettingsSlider &st);
bool setSectionsAndCheckChanged(std::vector<QString> &&sections);
bool setSectionsAndCheckChanged(
std::vector<TextWithEntities> &&sections,
const std::any &context,
Fn<bool()> paused);
void fitWidthToSections() override;
void setUnreadCount(int index, int unreadCount, bool muted);
@ -84,6 +87,7 @@ private:
std::optional<Ui::RoundRect> _bar;
std::optional<Ui::RoundRect> _barActive;
std::optional<QImage> _lockCache;
Fn<bool()> _emojiPaused;
int _reordering = 0;

View file

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/filters/edit_filter_box.h"
#include "boxes/premium_limits_box.h"
#include "core/application.h"
#include "core/ui_integration.h"
#include "data/data_chat_filters.h"
#include "data/data_peer_values.h" // Data::AmPremiumValue.
#include "data/data_premium_limits.h"
@ -20,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "settings/settings_folders.h"
#include "ui/power_saving.h"
#include "ui/ui_utility.h"
#include "ui/widgets/chat_filters_tabs_slider_reorder.h"
#include "ui/widgets/menu/menu_add_action_callback_factory.h"
@ -177,6 +179,7 @@ not_null<Ui::RpWidget*> AddChatFiltersTabsStrip(
not_null<Ui::RpWidget*> parent,
not_null<Main::Session*> session,
Fn<void(FilterId)> choose,
ChatHelpers::PauseReason pauseLevel,
Window::SessionController *controller,
bool trackActiveFilterAndUnreadAndReorder) {
@ -325,14 +328,22 @@ not_null<Ui::RpWidget*> AddChatFiltersTabsStrip(
if ((list.size() <= 1 && !slider->width()) || state->ignoreRefresh) {
return;
}
const auto context = Core::MarkedTextContext{
.session = session,
.customEmojiRepaint = [=] { slider->update(); },
};
const auto paused = [=] {
return On(PowerSaving::kEmojiChat)
|| controller->isGifPausedAtLeastFor(pauseLevel);
};
const auto sectionsChanged = slider->setSectionsAndCheckChanged(
ranges::views::all(
list
) | ranges::views::transform([](const Data::ChatFilter &filter) {
return filter.title().empty()
? tr::lng_filters_all_short(tr::now)
: filter.title().text; // todo filter emoji
}) | ranges::to_vector);
? TextWithEntities{ tr::lng_filters_all_short(tr::now) }
: filter.title();
}) | ranges::to_vector, context, paused);
if (!sectionsChanged) {
return;
}

View file

@ -7,6 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
namespace ChatHelpers {
enum class PauseReason;
} // namespace ChatHelpers
namespace Main {
class Session;
} // namespace Main
@ -25,6 +29,7 @@ not_null<Ui::RpWidget*> AddChatFiltersTabsStrip(
not_null<Ui::RpWidget*> parent,
not_null<Main::Session*> session,
Fn<void(FilterId)> choose,
ChatHelpers::PauseReason pauseLevel,
Window::SessionController *controller = nullptr,
bool trackActiveFilterAndUnreadAndReorder = false);

View file

@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_main_menu.h"
#include "window/window_peer_menu.h"
#include "main/main_session.h"
#include "core/ui_integration.h"
#include "data/data_session.h"
#include "data/data_chat_filters.h"
#include "data/data_user.h"
@ -26,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/menu/menu_add_action_callback_factory.h"
#include "ui/widgets/popup_menu.h"
#include "ui/boxes/confirm_box.h"
#include "ui/power_saving.h"
#include "ui/ui_utility.h"
#include "boxes/filters/edit_filter_box.h"
#include "boxes/premium_limits_box.h"
@ -46,7 +48,7 @@ FiltersMenu::FiltersMenu(
: _session(session)
, _parent(parent)
, _outer(_parent)
, _menu(&_outer, QString(), st::windowFiltersMainMenu)
, _menu(&_outer, TextWithEntities(), st::windowFiltersMainMenu)
, _scroll(&_outer)
, _container(
_scroll.setOwnedWidget(
@ -250,10 +252,22 @@ base::unique_qptr<Ui::SideBarButton> FiltersMenu::prepareButton(
TextWithEntities title,
Ui::FilterIcon icon,
bool toBeginning) {
const auto makeContext = [=](Fn<void()> update) {
return Core::MarkedTextContext{
.session = &_session->session(),
.customEmojiRepaint = std::move(update),
};
};
const auto paused = [=] {
return On(PowerSaving::kEmojiChat)
|| _session->isGifPausedAtLeastFor(Window::GifPauseReason::Any);
};
auto prepared = object_ptr<Ui::SideBarButton>(
container,
id ? title.text : tr::lng_filters_all(tr::now), // todo filter emoji
st::windowFiltersButton);
id ? title : TextWithEntities{ tr::lng_filters_all(tr::now) },
st::windowFiltersButton,
makeContext,
paused);
auto added = toBeginning
? container->insert(0, std::move(prepared))
: container->add(std::move(prepared));

View file

@ -2241,7 +2241,8 @@ QPointer<Ui::BoxContent> ShowForwardMessagesBox(
[=](FilterId id) {
*lastFilterId = id;
applyFilter(box, id);
});
},
Window::GifPauseReason::Layer);
chatsFilters->lower();
rpl::combine(
chatsFilters->heightValue(),

@ -1 +1 @@
Subproject commit ab8d8fcfd265f94b2a63f94a0f1796b907818b74
Subproject commit b063197b5d05de96754894630e45c8b3f95ade62