1463 lines
39 KiB
C++
1463 lines
39 KiB
C++
/*
|
|
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 "chat_helpers/tabbed_selector.h"
|
|
|
|
#include "chat_helpers/emoji_list_widget.h"
|
|
#include "chat_helpers/stickers_list_widget.h"
|
|
#include "chat_helpers/gifs_list_widget.h"
|
|
#include "menu/menu_send.h"
|
|
#include "ui/controls/tabbed_search.h"
|
|
#include "ui/widgets/buttons.h"
|
|
#include "ui/widgets/labels.h"
|
|
#include "ui/widgets/shadow.h"
|
|
#include "ui/widgets/discrete_sliders.h"
|
|
#include "ui/widgets/popup_menu.h"
|
|
#include "ui/widgets/scroll_area.h"
|
|
#include "ui/layers/box_content.h"
|
|
#include "ui/image/image_prepare.h"
|
|
#include "ui/cached_round_corners.h"
|
|
#include "ui/painter.h"
|
|
#include "window/window_session_controller.h"
|
|
#include "main/main_session.h"
|
|
#include "main/main_session_settings.h"
|
|
#include "storage/localstorage.h"
|
|
#include "data/data_channel.h"
|
|
#include "data/data_emoji_statuses.h"
|
|
#include "data/data_session.h"
|
|
#include "data/data_changes.h"
|
|
#include "data/stickers/data_stickers.h"
|
|
#include "data/stickers/data_custom_emoji.h" // AllowEmojiWithoutPremium.
|
|
#include "boxes/premium_preview_box.h"
|
|
#include "lang/lang_keys.h"
|
|
#include "mainwindow.h"
|
|
#include "apiwrap.h"
|
|
#include "styles/style_chat_helpers.h"
|
|
#include "styles/style_menu_icons.h"
|
|
|
|
namespace ChatHelpers {
|
|
|
|
class TabbedSelector::SlideAnimation : public Ui::RoundShadowAnimation {
|
|
public:
|
|
enum class Direction {
|
|
LeftToRight,
|
|
RightToLeft,
|
|
};
|
|
void setFinalImages(Direction direction, QImage &&left, QImage &&right, QRect inner, bool wasSectionIcons);
|
|
|
|
void start();
|
|
void paintFrame(QPainter &p, const style::EmojiPan &st, float64 dt, float64 opacity);
|
|
|
|
private:
|
|
Direction _direction = Direction::LeftToRight;
|
|
QPixmap _leftImage, _rightImage;
|
|
int _width = 0;
|
|
int _height = 0;
|
|
int _innerLeft = 0;
|
|
int _innerTop = 0;
|
|
int _innerRight = 0;
|
|
int _innerBottom = 0;
|
|
int _innerWidth = 0;
|
|
int _innerHeight = 0;
|
|
|
|
int _painterInnerLeft = 0;
|
|
int _painterInnerTop = 0;
|
|
int _painterInnerWidth = 0;
|
|
int _painterInnerBottom = 0;
|
|
int _painterCategoriesTop = 0;
|
|
int _painterInnerHeight = 0;
|
|
int _painterInnerRight = 0;
|
|
|
|
int _frameIntsPerLineAdd = 0;
|
|
bool _wasSectionIcons = false;
|
|
|
|
};
|
|
|
|
void TabbedSelector::SlideAnimation::setFinalImages(Direction direction, QImage &&left, QImage &&right, QRect inner, bool wasSectionIcons) {
|
|
Expects(!started());
|
|
_direction = direction;
|
|
_leftImage = QPixmap::fromImage(std::move(left).convertToFormat(QImage::Format_ARGB32_Premultiplied), Qt::ColorOnly);
|
|
_rightImage = QPixmap::fromImage(std::move(right).convertToFormat(QImage::Format_ARGB32_Premultiplied), Qt::ColorOnly);
|
|
|
|
Assert(!_leftImage.isNull());
|
|
Assert(!_rightImage.isNull());
|
|
_width = _leftImage.width();
|
|
_height = _rightImage.height();
|
|
Assert(!(_width % cIntRetinaFactor()));
|
|
Assert(!(_height % cIntRetinaFactor()));
|
|
Assert(_leftImage.devicePixelRatio() == _rightImage.devicePixelRatio());
|
|
Assert(_rightImage.width() == _width);
|
|
Assert(_rightImage.height() == _height);
|
|
Assert(QRect(0, 0, _width, _height).contains(inner));
|
|
_innerLeft = inner.x();
|
|
_innerTop = inner.y();
|
|
_innerWidth = inner.width();
|
|
_innerHeight = inner.height();
|
|
Assert(!(_innerLeft % cIntRetinaFactor()));
|
|
Assert(!(_innerTop % cIntRetinaFactor()));
|
|
Assert(!(_innerWidth % cIntRetinaFactor()));
|
|
Assert(!(_innerHeight % cIntRetinaFactor()));
|
|
_innerRight = _innerLeft + _innerWidth;
|
|
_innerBottom = _innerTop + _innerHeight;
|
|
|
|
_painterInnerLeft = _innerLeft / cIntRetinaFactor();
|
|
_painterInnerTop = _innerTop / cIntRetinaFactor();
|
|
_painterInnerRight = _innerRight / cIntRetinaFactor();
|
|
_painterInnerBottom = _innerBottom / cIntRetinaFactor();
|
|
_painterInnerWidth = _innerWidth / cIntRetinaFactor();
|
|
_painterInnerHeight = _innerHeight / cIntRetinaFactor();
|
|
_painterCategoriesTop = _painterInnerBottom - st::defaultEmojiPan.footer;
|
|
|
|
_wasSectionIcons = wasSectionIcons;
|
|
}
|
|
|
|
void TabbedSelector::SlideAnimation::start() {
|
|
Assert(!_leftImage.isNull());
|
|
Assert(!_rightImage.isNull());
|
|
RoundShadowAnimation::start(_width, _height, _leftImage.devicePixelRatio());
|
|
auto checkCorner = [this](const Corner &corner) {
|
|
if (!corner.valid()) return;
|
|
Assert(corner.width <= _innerWidth);
|
|
Assert(corner.height <= _innerHeight);
|
|
};
|
|
checkCorner(_topLeft);
|
|
checkCorner(_topRight);
|
|
checkCorner(_bottomLeft);
|
|
checkCorner(_bottomRight);
|
|
_frameIntsPerLineAdd = (_width - _innerWidth) + _frameIntsPerLineAdded;
|
|
}
|
|
|
|
void TabbedSelector::SlideAnimation::paintFrame(
|
|
QPainter &p,
|
|
const style::EmojiPan &st,
|
|
float64 dt,
|
|
float64 opacity) {
|
|
Expects(started());
|
|
Expects(dt >= 0.);
|
|
|
|
_frameAlpha = anim::interpolate(1, 256, opacity);
|
|
|
|
auto leftToRight = (_direction == Direction::LeftToRight);
|
|
|
|
auto easeOut = anim::easeOutCirc(1., dt);
|
|
auto easeIn = anim::easeInCirc(1., dt);
|
|
|
|
auto arrivingCoord = anim::interpolate(_innerWidth, 0, easeOut);
|
|
auto departingCoord = anim::interpolate(0, _innerWidth, easeIn);
|
|
if (auto decrease = (arrivingCoord % cIntRetinaFactor())) {
|
|
arrivingCoord -= decrease;
|
|
}
|
|
if (auto decrease = (departingCoord % cIntRetinaFactor())) {
|
|
departingCoord -= decrease;
|
|
}
|
|
auto arrivingAlpha = easeIn;
|
|
auto departingAlpha = 1. - easeOut;
|
|
auto leftCoord = (leftToRight ? arrivingCoord : departingCoord) * -1;
|
|
auto leftAlpha = (leftToRight ? arrivingAlpha : departingAlpha);
|
|
auto rightCoord = (leftToRight ? departingCoord : arrivingCoord);
|
|
auto rightAlpha = (leftToRight ? departingAlpha : arrivingAlpha);
|
|
|
|
// _innerLeft ..(left).. leftTo ..(both).. bothTo ..(none).. noneTo ..(right).. _innerRight
|
|
auto leftTo = _innerLeft
|
|
+ std::clamp(_innerWidth + leftCoord, 0, _innerWidth);
|
|
auto rightFrom = _innerLeft + std::clamp(rightCoord, 0, _innerWidth);
|
|
auto painterRightFrom = rightFrom / cIntRetinaFactor();
|
|
if (opacity < 1.) {
|
|
_frame.fill(Qt::transparent);
|
|
}
|
|
{
|
|
auto p = QPainter(&_frame);
|
|
p.setOpacity(opacity);
|
|
p.fillRect(_painterInnerLeft, _painterInnerTop, _painterInnerWidth, _painterCategoriesTop - _painterInnerTop, st.bg);
|
|
p.fillRect(_painterInnerLeft, _painterCategoriesTop, _painterInnerWidth, _painterInnerBottom - _painterCategoriesTop, _wasSectionIcons ? st.categoriesBg : st.bg);
|
|
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
|
if (leftTo > _innerLeft) {
|
|
p.setOpacity(opacity * leftAlpha);
|
|
p.drawPixmap(_painterInnerLeft, _painterInnerTop, _leftImage, _innerLeft - leftCoord, _innerTop, leftTo - _innerLeft, _innerHeight);
|
|
}
|
|
if (rightFrom < _innerRight) {
|
|
p.setOpacity(opacity * rightAlpha);
|
|
p.drawPixmap(painterRightFrom, _painterInnerTop, _rightImage, _innerLeft, _innerTop, _innerRight - rightFrom, _innerHeight);
|
|
}
|
|
}
|
|
|
|
// Draw corners
|
|
//paintCorner(_topLeft, _innerLeft, _innerTop);
|
|
//paintCorner(_topRight, _innerRight - _topRight.width, _innerTop);
|
|
paintCorner(_bottomLeft, _innerLeft, _innerBottom - _bottomLeft.height);
|
|
paintCorner(_bottomRight, _innerRight - _bottomRight.width, _innerBottom - _bottomRight.height);
|
|
|
|
// Draw shadow upon the transparent
|
|
auto outerLeft = _innerLeft;
|
|
auto outerTop = _innerTop;
|
|
auto outerRight = _innerRight;
|
|
auto outerBottom = _innerBottom;
|
|
if (_shadow.valid()) {
|
|
outerLeft -= _shadow.extend.left();
|
|
outerTop -= _shadow.extend.top();
|
|
outerRight += _shadow.extend.right();
|
|
outerBottom += _shadow.extend.bottom();
|
|
}
|
|
if (cIntRetinaFactor() > 1) {
|
|
if (auto skipLeft = (outerLeft % cIntRetinaFactor())) {
|
|
outerLeft -= skipLeft;
|
|
}
|
|
if (auto skipTop = (outerTop % cIntRetinaFactor())) {
|
|
outerTop -= skipTop;
|
|
}
|
|
if (auto skipRight = (outerRight % cIntRetinaFactor())) {
|
|
outerRight += (cIntRetinaFactor() - skipRight);
|
|
}
|
|
if (auto skipBottom = (outerBottom % cIntRetinaFactor())) {
|
|
outerBottom += (cIntRetinaFactor() - skipBottom);
|
|
}
|
|
}
|
|
|
|
if (opacity == 1.) {
|
|
// Fill above the frame top with transparent.
|
|
auto fillTopInts = (_frameInts + outerTop * _frameIntsPerLine + outerLeft);
|
|
auto fillWidth = (outerRight - outerLeft) * sizeof(uint32);
|
|
for (auto fillTop = _innerTop - outerTop; fillTop != 0; --fillTop) {
|
|
memset(fillTopInts, 0, fillWidth);
|
|
fillTopInts += _frameIntsPerLine;
|
|
}
|
|
|
|
// Fill to the left and to the right of the frame with transparent.
|
|
auto fillLeft = (_innerLeft - outerLeft) * sizeof(uint32);
|
|
auto fillRight = (outerRight - _innerRight) * sizeof(uint32);
|
|
if (fillLeft || fillRight) {
|
|
auto fillInts = _frameInts + _innerTop * _frameIntsPerLine;
|
|
for (auto y = _innerTop; y != _innerBottom; ++y) {
|
|
memset(fillInts + outerLeft, 0, fillLeft);
|
|
memset(fillInts + _innerRight, 0, fillRight);
|
|
fillInts += _frameIntsPerLine;
|
|
}
|
|
}
|
|
|
|
// Fill below the frame bottom with transparent.
|
|
auto fillBottomInts = (_frameInts + _innerBottom * _frameIntsPerLine + outerLeft);
|
|
for (auto fillBottom = outerBottom - _innerBottom; fillBottom != 0; --fillBottom) {
|
|
memset(fillBottomInts, 0, fillWidth);
|
|
fillBottomInts += _frameIntsPerLine;
|
|
}
|
|
}
|
|
if (_shadow.valid()) {
|
|
paintShadow(outerLeft, outerTop, outerRight, outerBottom);
|
|
}
|
|
|
|
// Debug
|
|
//auto frameInts = _frameInts;
|
|
//auto pattern = anim::shifted((static_cast<uint32>(0xFF) << 24) | (static_cast<uint32>(0xFF) << 16) | (static_cast<uint32>(0xFF) << 8) | static_cast<uint32>(0xFF));
|
|
//for (auto y = 0; y != _finalHeight; ++y) {
|
|
// for (auto x = 0; x != _finalWidth; ++x) {
|
|
// auto source = *frameInts;
|
|
// auto sourceAlpha = (source >> 24);
|
|
// *frameInts = anim::unshifted(anim::shifted(source) * 256 + pattern * (256 - sourceAlpha));
|
|
// ++frameInts;
|
|
// }
|
|
// frameInts += _frameIntsPerLineAdded;
|
|
//}
|
|
|
|
p.drawImage(outerLeft / cIntRetinaFactor(), outerTop / cIntRetinaFactor(), _frame, outerLeft, outerTop, outerRight - outerLeft, outerBottom - outerTop);
|
|
}
|
|
|
|
TabbedSelector::Tab::Tab(
|
|
SelectorTab type,
|
|
int index,
|
|
object_ptr<Inner> widget)
|
|
: _type(type)
|
|
, _index(index)
|
|
, _widget(std::move(widget))
|
|
, _weak(_widget)
|
|
, _footer(_widget ? _widget->createFooter() : nullptr) {
|
|
if (_footer) {
|
|
_footer->setParent(_widget->parentWidget());
|
|
}
|
|
}
|
|
|
|
object_ptr<TabbedSelector::Inner> TabbedSelector::Tab::takeWidget() {
|
|
return std::move(_widget);
|
|
}
|
|
|
|
void TabbedSelector::Tab::returnWidget(object_ptr<Inner> widget) {
|
|
Expects(widget == _weak);
|
|
|
|
_widget = std::move(widget);
|
|
}
|
|
|
|
void TabbedSelector::Tab::saveScrollTop() {
|
|
Expects(widget() != nullptr);
|
|
|
|
_scrollTop = widget()->getVisibleTop();
|
|
}
|
|
|
|
std::unique_ptr<Ui::TabbedSearch> MakeSearch(
|
|
not_null<Ui::RpWidget*> parent,
|
|
const style::EmojiPan &st,
|
|
Fn<void(std::vector<QString>&&)> callback,
|
|
not_null<Main::Session*> session,
|
|
bool statusCategories,
|
|
bool profilePhotoCategories) {
|
|
using Descriptor = Ui::SearchDescriptor;
|
|
const auto owner = &session->data();
|
|
auto result = std::make_unique<Ui::TabbedSearch>(parent, st, Descriptor{
|
|
.st = st.search,
|
|
.groups = (profilePhotoCategories
|
|
? owner->emojiStatuses().profilePhotoGroupsValue()
|
|
: statusCategories
|
|
? owner->emojiStatuses().statusGroupsValue()
|
|
: owner->emojiStatuses().emojiGroupsValue()),
|
|
.customEmojiFactory = owner->customEmojiManager().factory(
|
|
Data::CustomEmojiManager::SizeTag::SetIcon,
|
|
Ui::SearchWithGroups::IconSizeOverride())
|
|
});
|
|
|
|
result->queryValue(
|
|
) | rpl::skip(1) | rpl::start_with_next(
|
|
std::move(callback),
|
|
parent->lifetime());
|
|
|
|
return result;
|
|
}
|
|
|
|
TabbedSelector::TabbedSelector(
|
|
QWidget *parent,
|
|
std::shared_ptr<Show> show,
|
|
PauseReason level,
|
|
Mode mode)
|
|
: TabbedSelector(parent, {
|
|
.show = std::move(show),
|
|
.st = ((mode == Mode::EmojiStatus
|
|
|| mode == Mode::ChannelStatus
|
|
|| mode == Mode::BackgroundEmoji
|
|
|| mode == Mode::FullReactions)
|
|
? st::statusEmojiPan
|
|
: (mode == Mode::RecentReactions)
|
|
? st::backgroundEmojiPan
|
|
: st::defaultEmojiPan),
|
|
.level = level,
|
|
.mode = mode,
|
|
}) {
|
|
}
|
|
|
|
TabbedSelector::TabbedSelector(
|
|
QWidget *parent,
|
|
TabbedSelectorDescriptor &&descriptor)
|
|
: RpWidget(parent)
|
|
, _st(descriptor.st)
|
|
, _features(descriptor.features)
|
|
, _show(std::move(descriptor.show))
|
|
, _level(descriptor.level)
|
|
, _customTextColor(std::move(descriptor.customTextColor))
|
|
, _mode(descriptor.mode)
|
|
, _panelRounding(Ui::PrepareCornerPixmaps(st::emojiPanRadius, _st.bg))
|
|
, _categoriesRounding(
|
|
Ui::PrepareCornerPixmaps(st::emojiPanRadius, _st.categoriesBg))
|
|
, _topShadow(full() ? object_ptr<Ui::PlainShadow>(this) : nullptr)
|
|
, _bottomShadow(this)
|
|
, _scroll(this, st::emojiScroll)
|
|
, _tabs([&] {
|
|
std::vector<Tab> tabs;
|
|
if (full()) {
|
|
tabs.reserve(3);
|
|
tabs.push_back(createTab(SelectorTab::Emoji, 0));
|
|
tabs.push_back(createTab(SelectorTab::Stickers, 1));
|
|
tabs.push_back(createTab(SelectorTab::Gifs, 2));
|
|
} else if (mediaEditor()) {
|
|
tabs.reserve(2);
|
|
tabs.push_back(createTab(SelectorTab::Stickers, 0));
|
|
tabs.push_back(createTab(SelectorTab::Masks, 1));
|
|
} else {
|
|
tabs.reserve(1);
|
|
tabs.push_back(createTab(SelectorTab::Emoji, 0));
|
|
}
|
|
return tabs;
|
|
}())
|
|
, _currentTabType(full()
|
|
? session().settings().selectorTab()
|
|
: mediaEditor()
|
|
? SelectorTab::Stickers
|
|
: SelectorTab::Emoji)
|
|
, _hasEmojiTab(ranges::contains(_tabs, SelectorTab::Emoji, &Tab::type))
|
|
, _hasStickersTab(ranges::contains(_tabs, SelectorTab::Stickers, &Tab::type))
|
|
, _hasGifsTab(ranges::contains(_tabs, SelectorTab::Gifs, &Tab::type))
|
|
, _hasMasksTab(ranges::contains(_tabs, SelectorTab::Masks, &Tab::type))
|
|
, _tabbed(_tabs.size() > 1) {
|
|
resize(st::emojiPanWidth, st::emojiPanMaxHeight);
|
|
|
|
for (auto &tab : _tabs) {
|
|
if (tab.hasFooter()) {
|
|
tab.footer()->hide();
|
|
} else {
|
|
_noFooter = true;
|
|
}
|
|
tab.widget()->hide();
|
|
}
|
|
if (tabbed()) {
|
|
createTabsSlider();
|
|
}
|
|
setWidgetToScrollArea();
|
|
|
|
for (auto &tab : _tabs) {
|
|
const auto widget = tab.widget();
|
|
|
|
widget->scrollToRequests(
|
|
) | rpl::start_with_next([=, tab = &tab](int y) {
|
|
if (tab == currentTab()) {
|
|
scrollToY(y);
|
|
} else {
|
|
tab->saveScrollTop(y);
|
|
}
|
|
}, widget->lifetime());
|
|
|
|
widget->disableScrollRequests(
|
|
) | rpl::start_with_next([=, tab = &tab](bool disabled) {
|
|
if (tab == currentTab()) {
|
|
_scroll->disableScroll(disabled);
|
|
}
|
|
}, widget->lifetime());
|
|
}
|
|
|
|
rpl::merge(
|
|
(hasStickersTab()
|
|
? stickers()->scrollUpdated() | rpl::map_to(0)
|
|
: rpl::never<int>() | rpl::type_erased()),
|
|
_scroll->scrollTopChanges()
|
|
) | rpl::start_with_next([=] {
|
|
handleScroll();
|
|
}, lifetime());
|
|
|
|
if (_topShadow) {
|
|
_topShadow->raise();
|
|
}
|
|
_bottomShadow->raise();
|
|
if (_tabsSlider) {
|
|
_tabsSlider->raise();
|
|
}
|
|
|
|
if (hasStickersTab()
|
|
|| hasGifsTab()
|
|
|| (hasEmojiTab() && _mode == Mode::Full)) {
|
|
session().changes().peerUpdates(
|
|
Data::PeerUpdate::Flag::Rights
|
|
) | rpl::filter([=](const Data::PeerUpdate &update) {
|
|
return (update.peer.get() == _currentPeer);
|
|
}) | rpl::start_with_next([=] {
|
|
checkRestrictedPeer();
|
|
}, lifetime());
|
|
}
|
|
|
|
if (hasStickersTab()) {
|
|
session().data().stickers().stickerSetInstalled(
|
|
) | rpl::start_with_next([=](uint64 setId) {
|
|
_tabsSlider->setActiveSection(indexByType(SelectorTab::Stickers));
|
|
stickers()->showStickerSet(setId);
|
|
_showRequests.fire({});
|
|
}, lifetime());
|
|
|
|
rpl::merge(
|
|
session().premiumPossibleValue() | rpl::to_empty,
|
|
session().data().stickers().updated(hasMasksTab()
|
|
? Data::StickersType::Masks
|
|
: Data::StickersType::Stickers)
|
|
) | rpl::start_with_next([=] {
|
|
refreshStickers();
|
|
}, lifetime());
|
|
}
|
|
|
|
style::PaletteChanged(
|
|
) | rpl::start_with_next([=] {
|
|
_panelRounding = Ui::PrepareCornerPixmaps(
|
|
st::emojiPanRadius,
|
|
_st.bg);
|
|
_categoriesRounding = Ui::PrepareCornerPixmaps(
|
|
st::emojiPanRadius,
|
|
_st.categoriesBg);
|
|
}, lifetime());
|
|
|
|
if (hasEmojiTab()) {
|
|
session().data().stickers().emojiSetInstalled(
|
|
) | rpl::start_with_next([=](uint64 setId) {
|
|
_tabsSlider->setActiveSection(indexByType(SelectorTab::Emoji));
|
|
emoji()->showSet(setId);
|
|
_showRequests.fire({});
|
|
}, lifetime());
|
|
|
|
emoji()->refreshEmoji();
|
|
}
|
|
//setAttribute(Qt::WA_AcceptTouchEvents);
|
|
setAttribute(Qt::WA_OpaquePaintEvent, false);
|
|
showAll();
|
|
hide();
|
|
}
|
|
|
|
TabbedSelector::~TabbedSelector() = default;
|
|
|
|
const style::EmojiPan &TabbedSelector::st() const {
|
|
return _st;
|
|
}
|
|
|
|
Main::Session &TabbedSelector::session() const {
|
|
return _show->session();
|
|
}
|
|
|
|
PauseReason TabbedSelector::level() const {
|
|
return _level;
|
|
}
|
|
|
|
TabbedSelector::Tab TabbedSelector::createTab(SelectorTab type, int index) {
|
|
auto createWidget = [&]() -> object_ptr<Inner> {
|
|
const auto paused = [show = _show, level = _level] {
|
|
return show->paused(level);
|
|
};
|
|
switch (type) {
|
|
case SelectorTab::Emoji: {
|
|
using EmojiMode = EmojiListWidget::Mode;
|
|
using Descriptor = EmojiListDescriptor;
|
|
return object_ptr<EmojiListWidget>(this, Descriptor{
|
|
.show = _show,
|
|
.mode = (_mode == Mode::EmojiStatus
|
|
? EmojiMode::EmojiStatus
|
|
: _mode == Mode::ChannelStatus
|
|
? EmojiMode::ChannelStatus
|
|
: _mode == Mode::BackgroundEmoji
|
|
? EmojiMode::BackgroundEmoji
|
|
: _mode == Mode::FullReactions
|
|
? EmojiMode::FullReactions
|
|
: _mode == Mode::RecentReactions
|
|
? EmojiMode::RecentReactions
|
|
: EmojiMode::Full),
|
|
.customTextColor = _customTextColor,
|
|
.paused = paused,
|
|
.st = &_st,
|
|
.features = _features,
|
|
});
|
|
}
|
|
case SelectorTab::Stickers: {
|
|
using StickersMode = StickersListWidget::Mode;
|
|
using Descriptor = StickersListDescriptor;
|
|
return object_ptr<StickersListWidget>(this, Descriptor{
|
|
.show = _show,
|
|
.mode = StickersMode::Full,
|
|
.paused = paused,
|
|
.st = &_st,
|
|
.features = _features,
|
|
});
|
|
}
|
|
case SelectorTab::Gifs: {
|
|
using Descriptor = GifsListDescriptor;
|
|
return object_ptr<GifsListWidget>(this, Descriptor{
|
|
.show = _show,
|
|
.paused = paused,
|
|
.st = &_st,
|
|
});
|
|
}
|
|
case SelectorTab::Masks: {
|
|
using StickersMode = StickersListWidget::Mode;
|
|
using Descriptor = StickersListDescriptor;
|
|
return object_ptr<StickersListWidget>(this, Descriptor{
|
|
.show = _show,
|
|
.mode = StickersMode::Masks,
|
|
.paused = paused,
|
|
.st = &_st,
|
|
.features = _features,
|
|
});
|
|
}
|
|
}
|
|
Unexpected("Type in TabbedSelector::createTab.");
|
|
};
|
|
return Tab{ type, index, createWidget() };
|
|
}
|
|
|
|
bool TabbedSelector::full() const {
|
|
return (_mode == Mode::Full);
|
|
}
|
|
|
|
bool TabbedSelector::mediaEditor() const {
|
|
return (_mode == Mode::MediaEditor);
|
|
}
|
|
|
|
bool TabbedSelector::tabbed() const {
|
|
return _tabbed;
|
|
}
|
|
|
|
bool TabbedSelector::hasEmojiTab() const {
|
|
return _hasEmojiTab;
|
|
}
|
|
|
|
bool TabbedSelector::hasStickersTab() const {
|
|
return _hasStickersTab;
|
|
}
|
|
|
|
bool TabbedSelector::hasGifsTab() const {
|
|
return _hasGifsTab;
|
|
}
|
|
|
|
bool TabbedSelector::hasMasksTab() const {
|
|
return _hasMasksTab;
|
|
}
|
|
|
|
rpl::producer<EmojiChosen> TabbedSelector::emojiChosen() const {
|
|
return emoji()->chosen();
|
|
}
|
|
|
|
rpl::producer<FileChosen> TabbedSelector::customEmojiChosen() const {
|
|
return emoji()->customChosen();
|
|
}
|
|
|
|
rpl::producer<FileChosen> TabbedSelector::fileChosen() const {
|
|
auto never = rpl::never<FileChosen>(
|
|
) | rpl::type_erased();
|
|
return rpl::merge(
|
|
hasStickersTab() ? stickers()->chosen() : never,
|
|
hasGifsTab() ? gifs()->fileChosen() : never,
|
|
hasMasksTab() ? masks()->chosen() : never);
|
|
}
|
|
|
|
rpl::producer<PhotoChosen> TabbedSelector::photoChosen() const {
|
|
return hasGifsTab() ? gifs()->photoChosen() : nullptr;
|
|
}
|
|
|
|
auto TabbedSelector::inlineResultChosen() const
|
|
-> rpl::producer<InlineChosen> {
|
|
return hasGifsTab() ? gifs()->inlineResultChosen() : nullptr;
|
|
}
|
|
|
|
auto TabbedSelector::choosingStickerUpdated() const
|
|
-> rpl::producer<TabbedSelector::Action>{
|
|
return hasStickersTab()
|
|
? stickers()->choosingUpdated()
|
|
: rpl::never<Action>();
|
|
}
|
|
|
|
rpl::producer<> TabbedSelector::cancelled() const {
|
|
return hasGifsTab() ? gifs()->cancelRequests() : nullptr;
|
|
}
|
|
|
|
rpl::producer<> TabbedSelector::checkForHide() const {
|
|
auto never = rpl::never<>();
|
|
return rpl::merge(
|
|
hasStickersTab() ? stickers()->checkForHide() : never,
|
|
hasMasksTab() ? masks()->checkForHide() : never,
|
|
hasEmojiTab() ? emoji()->checkForHide() : never);
|
|
}
|
|
|
|
rpl::producer<> TabbedSelector::slideFinished() const {
|
|
return _slideFinished.events();
|
|
}
|
|
|
|
void TabbedSelector::updateTabsSliderGeometry() {
|
|
if (!_tabsSlider) {
|
|
return;
|
|
}
|
|
const auto w = mediaEditor() && hasMasksTab() && masks()->mySetsEmpty()
|
|
? width() / 2
|
|
: width();
|
|
_tabsSlider->resizeToWidth(w);
|
|
_tabsSlider->moveToLeft(0, 0);
|
|
}
|
|
|
|
void TabbedSelector::resizeEvent(QResizeEvent *e) {
|
|
updateTabsSliderGeometry();
|
|
if (_topShadow && _tabsSlider) {
|
|
_topShadow->setGeometry(
|
|
_tabsSlider->x(),
|
|
_tabsSlider->bottomNoMargins() - st::lineWidth,
|
|
_tabsSlider->width(),
|
|
st::lineWidth);
|
|
}
|
|
updateScrollGeometry(e->oldSize());
|
|
updateRestrictedLabelGeometry();
|
|
updateFooterGeometry();
|
|
update();
|
|
}
|
|
|
|
void TabbedSelector::updateScrollGeometry(QSize oldSize) {
|
|
auto scrollWidth = width() - st::emojiPanRadius;
|
|
auto scrollHeight = height() - scrollTop() - scrollBottom();
|
|
auto inner = currentTab()->widget();
|
|
auto innerWidth = scrollWidth - st::emojiScroll.width;
|
|
auto setScrollGeometry = [&] {
|
|
_scroll->setGeometryToLeft(
|
|
st::emojiPanRadius,
|
|
scrollTop(),
|
|
scrollWidth,
|
|
scrollHeight);
|
|
};
|
|
auto setInnerGeometry = [&] {
|
|
auto scrollTop = _scroll->scrollTop();
|
|
auto scrollBottom = scrollTop + scrollHeight;
|
|
inner->setMinimalHeight(innerWidth, scrollHeight);
|
|
inner->setVisibleTopBottom(scrollTop, scrollBottom);
|
|
};
|
|
if (oldSize.height() > height()) {
|
|
setScrollGeometry();
|
|
setInnerGeometry();
|
|
} else {
|
|
setInnerGeometry();
|
|
setScrollGeometry();
|
|
}
|
|
_bottomShadow->setGeometry(
|
|
0,
|
|
_scroll->y() + (_dropDown ? 0 : (_scroll->height() - st::lineWidth)),
|
|
width(),
|
|
st::lineWidth);
|
|
}
|
|
|
|
void TabbedSelector::updateFooterGeometry() {
|
|
_footerTop = _dropDown
|
|
? 0
|
|
: _noFooter
|
|
? (height() - _roundRadius)
|
|
: (height() - _st.footer);
|
|
for (auto &tab : _tabs) {
|
|
if (tab.hasFooter()) {
|
|
tab.footer()->resizeToWidth(width());
|
|
tab.footer()->moveToLeft(0, _footerTop);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TabbedSelector::updateRestrictedLabelGeometry() {
|
|
if (!_restrictedLabel) {
|
|
return;
|
|
}
|
|
|
|
auto labelWidth = width() - st::stickerPanPadding * 2;
|
|
_restrictedLabel->resizeToWidth(labelWidth);
|
|
_restrictedLabel->moveToLeft(
|
|
(width() - _restrictedLabel->width()) / 2,
|
|
(height() / 3 - _restrictedLabel->height() / 2));
|
|
}
|
|
|
|
void TabbedSelector::paintEvent(QPaintEvent *e) {
|
|
auto p = QPainter(this);
|
|
|
|
auto switching = (_slideAnimation != nullptr);
|
|
if (switching) {
|
|
paintSlideFrame(p);
|
|
if (!_a_slide.animating()) {
|
|
_slideAnimation.reset();
|
|
afterShown();
|
|
_slideFinished.fire({});
|
|
}
|
|
} else {
|
|
paintContent(p);
|
|
}
|
|
}
|
|
|
|
void TabbedSelector::paintSlideFrame(QPainter &p) {
|
|
if (_roundRadius > 0) {
|
|
paintBgRoundedPart(p);
|
|
} else if (_tabsSlider) {
|
|
p.fillRect(0, 0, width(), _tabsSlider->height(), _st.bg);
|
|
}
|
|
auto slideDt = _a_slide.value(1.);
|
|
_slideAnimation->paintFrame(p, _st, slideDt, 1.);
|
|
}
|
|
|
|
void TabbedSelector::paintBgRoundedPart(QPainter &p) {
|
|
const auto fill = _dropDown
|
|
? QRect(0, height() - _roundRadius, width(), _roundRadius)
|
|
: _tabsSlider
|
|
? QRect(0, 0, width(), _tabsSlider->height())
|
|
: QRect(0, 0, width(), _roundRadius);
|
|
Ui::FillRoundRect(p, fill, _st.bg, {
|
|
.p = {
|
|
_dropDown ? QPixmap() : _panelRounding.p[0],
|
|
_dropDown ? QPixmap() : _panelRounding.p[1],
|
|
_dropDown ? _panelRounding.p[2] : QPixmap(),
|
|
_dropDown ? _panelRounding.p[3] : QPixmap(),
|
|
},
|
|
});
|
|
}
|
|
|
|
void TabbedSelector::paintContent(QPainter &p) {
|
|
const auto &footerBg = hasSectionIcons() ? _st.categoriesBg : _st.bg;
|
|
if (_roundRadius > 0) {
|
|
paintBgRoundedPart(p);
|
|
|
|
const auto &pixmaps = hasSectionIcons()
|
|
? _categoriesRounding
|
|
: _panelRounding;
|
|
const auto footerPart = QRect(
|
|
0,
|
|
_footerTop,
|
|
width(),
|
|
_noFooter ? _roundRadius : _st.footer);
|
|
Ui::FillRoundRect(p, footerPart, footerBg, {
|
|
.p = {
|
|
_dropDown ? pixmaps.p[0] : QPixmap(),
|
|
_dropDown ? pixmaps.p[1] : QPixmap(),
|
|
_dropDown ? QPixmap() : pixmaps.p[2],
|
|
_dropDown ? QPixmap() : pixmaps.p[3],
|
|
},
|
|
});
|
|
} else {
|
|
if (_tabsSlider) {
|
|
p.fillRect(0, 0, width(), _tabsSlider->height(), _st.bg);
|
|
}
|
|
p.fillRect(0, _footerTop, width(), _st.footer, footerBg);
|
|
}
|
|
|
|
auto sidesTop = marginTop();
|
|
auto sidesHeight = height() - sidesTop - marginBottom();
|
|
if (_restrictedLabel) {
|
|
p.fillRect(0, sidesTop, width(), sidesHeight, st::emojiPanBg);
|
|
} else {
|
|
p.fillRect(
|
|
myrtlrect(
|
|
width() - st::emojiScroll.width,
|
|
sidesTop,
|
|
st::emojiScroll.width,
|
|
sidesHeight),
|
|
_st.bg);
|
|
p.fillRect(
|
|
myrtlrect(0, sidesTop, st::emojiPanRadius, sidesHeight),
|
|
_st.bg);
|
|
}
|
|
}
|
|
|
|
int TabbedSelector::marginTop() const {
|
|
return (_dropDown && !_noFooter)
|
|
? _st.footer
|
|
: _tabsSlider
|
|
? (_tabsSlider->height() - st::lineWidth)
|
|
: _roundRadius;
|
|
}
|
|
|
|
int TabbedSelector::scrollTop() const {
|
|
return tabbed()
|
|
? marginTop()
|
|
: (_dropDown && !_noFooter)
|
|
? _st.footer
|
|
: 0;
|
|
}
|
|
|
|
int TabbedSelector::marginBottom() const {
|
|
return (_dropDown || _noFooter) ? _roundRadius : _st.footer;
|
|
}
|
|
|
|
int TabbedSelector::scrollBottom() const {
|
|
return (_dropDown || _noFooter) ? 0 : marginBottom();
|
|
}
|
|
|
|
void TabbedSelector::refreshStickers() {
|
|
if (hasStickersTab()) {
|
|
stickers()->refreshStickers();
|
|
if (isHidden() || _currentTabType != SelectorTab::Stickers) {
|
|
stickers()->preloadImages();
|
|
}
|
|
}
|
|
if (hasMasksTab()) {
|
|
const auto masksList = masks();
|
|
masksList->refreshStickers();
|
|
if (isHidden() || _currentTabType != SelectorTab::Masks) {
|
|
masksList->preloadImages();
|
|
}
|
|
|
|
fillTabsSliderSections();
|
|
updateTabsSliderGeometry();
|
|
if (hasStickersTab() && masksList->mySetsEmpty()) {
|
|
_tabsSlider->setActiveSection(indexByType(SelectorTab::Stickers));
|
|
}
|
|
}
|
|
}
|
|
|
|
bool TabbedSelector::preventAutoHide() const {
|
|
return (hasStickersTab() && stickers()->preventAutoHide())
|
|
|| (hasMasksTab() && masks()->preventAutoHide())
|
|
|| (hasEmojiTab() && emoji()->preventAutoHide())
|
|
|| hasMenu();
|
|
}
|
|
|
|
bool TabbedSelector::hasMenu() const {
|
|
return (_menu && !_menu->empty());
|
|
}
|
|
|
|
QImage TabbedSelector::grabForAnimation() {
|
|
auto slideAnimationData = base::take(_slideAnimation);
|
|
auto slideAnimation = base::take(_a_slide);
|
|
|
|
showAll();
|
|
if (_topShadow) {
|
|
_topShadow->hide();
|
|
}
|
|
if (_tabsSlider) {
|
|
_tabsSlider->hide();
|
|
}
|
|
Ui::SendPendingMoveResizeEvents(this);
|
|
|
|
auto result = QImage(
|
|
size() * cIntRetinaFactor(),
|
|
QImage::Format_ARGB32_Premultiplied);
|
|
result.setDevicePixelRatio(cRetinaFactor());
|
|
result.fill(Qt::transparent);
|
|
render(&result);
|
|
|
|
_a_slide = base::take(slideAnimation);
|
|
_slideAnimation = base::take(slideAnimationData);
|
|
|
|
return result;
|
|
}
|
|
|
|
bool TabbedSelector::floatPlayerHandleWheelEvent(QEvent *e) {
|
|
return _scroll->viewportEvent(e);
|
|
}
|
|
|
|
QRect TabbedSelector::floatPlayerAvailableRect() const {
|
|
return mapToGlobal(_scroll->geometry());
|
|
}
|
|
|
|
void TabbedSelector::hideFinished() {
|
|
for (auto &tab : _tabs) {
|
|
tab.widget()->panelHideFinished();
|
|
}
|
|
_a_slide.stop();
|
|
_slideAnimation.reset();
|
|
}
|
|
|
|
void TabbedSelector::showStarted() {
|
|
if (hasStickersTab()) {
|
|
session().api().updateStickers();
|
|
}
|
|
if (hasMasksTab()) {
|
|
session().api().updateMasks();
|
|
}
|
|
if (hasEmojiTab()) {
|
|
session().api().updateCustomEmoji();
|
|
}
|
|
if (hasGifsTab()) {
|
|
session().api().updateSavedGifs();
|
|
}
|
|
currentTab()->widget()->refreshRecent();
|
|
currentTab()->widget()->preloadImages();
|
|
_a_slide.stop();
|
|
_slideAnimation.reset();
|
|
showAll();
|
|
}
|
|
|
|
void TabbedSelector::beforeHiding() {
|
|
if (!_scroll->isHidden()) {
|
|
currentTab()->widget()->beforeHiding();
|
|
if (_beforeHidingCallback) {
|
|
_beforeHidingCallback(_currentTabType);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TabbedSelector::afterShown() {
|
|
if (!_a_slide.animating()) {
|
|
showAll();
|
|
currentTab()->widget()->afterShown();
|
|
if (_afterShownCallback) {
|
|
_afterShownCallback(_currentTabType);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TabbedSelector::setCurrentPeer(PeerData *peer) {
|
|
if (hasGifsTab()) {
|
|
gifs()->setInlineQueryPeer(peer);
|
|
}
|
|
_currentPeer = peer;
|
|
checkRestrictedPeer();
|
|
if (hasEmojiTab()) {
|
|
emoji()->showMegagroupSet(peer ? peer->asMegagroup() : nullptr);
|
|
}
|
|
if (hasStickersTab()) {
|
|
stickers()->showMegagroupSet(peer ? peer->asMegagroup() : nullptr);
|
|
}
|
|
setAllowEmojiWithoutPremium(
|
|
peer && Data::AllowEmojiWithoutPremium(peer));
|
|
}
|
|
|
|
void TabbedSelector::provideRecentEmoji(
|
|
const std::vector<DocumentId> &customRecentList) {
|
|
for (const auto &tab : _tabs) {
|
|
if (tab.type() == SelectorTab::Emoji) {
|
|
const auto emoji = static_cast<EmojiListWidget*>(tab.widget());
|
|
emoji->provideRecent(customRecentList);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TabbedSelector::checkRestrictedPeer() {
|
|
if (_currentPeer) {
|
|
const auto error = (_currentTabType == SelectorTab::Stickers)
|
|
? Data::RestrictionError(
|
|
_currentPeer,
|
|
ChatRestriction::SendStickers)
|
|
: (_currentTabType == SelectorTab::Gifs)
|
|
? Data::RestrictionError(
|
|
_currentPeer,
|
|
ChatRestriction::SendGifs)
|
|
: (_currentTabType == SelectorTab::Emoji && _mode == Mode::Full)
|
|
? ((true || Data::RestrictionError(
|
|
_currentPeer, // We don't allow input if texts are forbidden.
|
|
ChatRestriction::SendInline))
|
|
? Data::RestrictionError(
|
|
_currentPeer,
|
|
ChatRestriction::SendOther)
|
|
: std::nullopt)
|
|
: std::nullopt;
|
|
if (error) {
|
|
if (!_restrictedLabel) {
|
|
_restrictedLabel.create(
|
|
this,
|
|
*error,
|
|
st::stickersRestrictedLabel);
|
|
_restrictedLabel->show();
|
|
updateRestrictedLabelGeometry();
|
|
currentTab()->footer()->hide();
|
|
_scroll->hide();
|
|
_bottomShadow->hide();
|
|
update();
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
if (_restrictedLabel) {
|
|
_restrictedLabel.destroy();
|
|
if (!_a_slide.animating()) {
|
|
currentTab()->footer()->show();
|
|
_scroll->show();
|
|
_bottomShadow->setVisible(_mode == Mode::EmojiStatus);
|
|
update();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool TabbedSelector::isRestrictedView() {
|
|
checkRestrictedPeer();
|
|
return (_restrictedLabel != nullptr);
|
|
}
|
|
|
|
void TabbedSelector::showAll() {
|
|
if (isRestrictedView()) {
|
|
_restrictedLabel->show();
|
|
} else {
|
|
if (currentTab()->hasFooter()) {
|
|
currentTab()->footer()->show();
|
|
}
|
|
_scroll->show();
|
|
_bottomShadow->setVisible(_mode == Mode::EmojiStatus);
|
|
}
|
|
if (_topShadow) {
|
|
_topShadow->show();
|
|
}
|
|
if (_tabsSlider) {
|
|
_tabsSlider->show();
|
|
}
|
|
}
|
|
|
|
void TabbedSelector::hideForSliding() {
|
|
hideChildren();
|
|
if (_topShadow) {
|
|
_topShadow->show();
|
|
}
|
|
if (_tabsSlider) {
|
|
_tabsSlider->show();
|
|
}
|
|
currentTab()->widget()->clearSelection();
|
|
}
|
|
|
|
void TabbedSelector::handleScroll() {
|
|
auto scrollTop = _scroll->scrollTop();
|
|
auto scrollBottom = scrollTop + _scroll->height();
|
|
currentTab()->widget()->setVisibleTopBottom(scrollTop, scrollBottom);
|
|
}
|
|
|
|
void TabbedSelector::setRoundRadius(int radius) {
|
|
_roundRadius = radius;
|
|
if (_tabsSlider) {
|
|
_tabsSlider->setRippleTopRoundRadius(_roundRadius);
|
|
}
|
|
}
|
|
|
|
void TabbedSelector::setAllowEmojiWithoutPremium(bool allow) {
|
|
for (const auto &tab : _tabs) {
|
|
if (tab.type() == SelectorTab::Emoji) {
|
|
const auto emoji = static_cast<EmojiListWidget*>(tab.widget());
|
|
emoji->setAllowWithoutPremium(allow);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TabbedSelector::createTabsSlider() {
|
|
_tabsSlider.create(this, _st.tabs);
|
|
|
|
fillTabsSliderSections();
|
|
|
|
_tabsSlider->setActiveSectionFast(indexByType(_currentTabType));
|
|
_tabsSlider->sectionActivated(
|
|
) | rpl::start_with_next([=] {
|
|
switchTab();
|
|
}, lifetime());
|
|
}
|
|
|
|
void TabbedSelector::fillTabsSliderSections() {
|
|
if (!_tabsSlider) {
|
|
return;
|
|
}
|
|
|
|
const auto sections = ranges::views::all(
|
|
_tabs
|
|
) | ranges::views::filter([&](const Tab &tab) {
|
|
return (tab.type() == SelectorTab::Masks)
|
|
? !masks()->mySetsEmpty()
|
|
: true;
|
|
}) | ranges::views::transform([&](const Tab &tab) {
|
|
return [&] {
|
|
switch (tab.type()) {
|
|
case SelectorTab::Emoji:
|
|
return tr::lng_switch_emoji;
|
|
case SelectorTab::Stickers:
|
|
return tr::lng_switch_stickers;
|
|
case SelectorTab::Gifs:
|
|
return tr::lng_switch_gifs;
|
|
case SelectorTab::Masks:
|
|
return tr::lng_switch_masks;
|
|
}
|
|
Unexpected("SelectorTab value in fillTabsSliderSections.");
|
|
}()(tr::now);
|
|
}) | ranges::to_vector;
|
|
_tabsSlider->setSections(sections);
|
|
}
|
|
|
|
bool TabbedSelector::hasSectionIcons() const {
|
|
return !_restrictedLabel && !_noFooter;
|
|
}
|
|
|
|
void TabbedSelector::switchTab() {
|
|
Expects(tabbed());
|
|
|
|
const auto tab = _tabsSlider->activeSection();
|
|
Assert(tab >= 0 && tab < _tabs.size());
|
|
const auto newTabType = typeByIndex(tab);
|
|
if (_currentTabType == newTabType) {
|
|
_scroll->scrollToY(0);
|
|
return;
|
|
}
|
|
|
|
const auto wasSectionIcons = hasSectionIcons();
|
|
const auto wasIndex = indexByType(_currentTabType);
|
|
currentTab()->saveScrollTop();
|
|
|
|
beforeHiding();
|
|
|
|
auto wasCache = grabForAnimation();
|
|
|
|
auto widget = _scroll->takeWidget<Inner>();
|
|
widget->setParent(this);
|
|
widget->hide();
|
|
if (currentTab()->hasFooter()) {
|
|
currentTab()->footer()->hide();
|
|
}
|
|
currentTab()->returnWidget(std::move(widget));
|
|
|
|
_currentTabType = newTabType;
|
|
_restrictedLabel.destroy();
|
|
checkRestrictedPeer();
|
|
|
|
currentTab()->widget()->refreshRecent();
|
|
currentTab()->widget()->preloadImages();
|
|
setWidgetToScrollArea();
|
|
|
|
auto nowCache = grabForAnimation();
|
|
|
|
auto direction = (wasIndex > indexByType(_currentTabType))
|
|
? SlideAnimation::Direction::LeftToRight
|
|
: SlideAnimation::Direction::RightToLeft;
|
|
if (direction == SlideAnimation::Direction::LeftToRight) {
|
|
std::swap(wasCache, nowCache);
|
|
}
|
|
_slideAnimation = std::make_unique<SlideAnimation>();
|
|
const auto slidingRect = QRect(
|
|
0,
|
|
_scroll->y() * cIntRetinaFactor(),
|
|
width() * cIntRetinaFactor(),
|
|
(height() - _scroll->y()) * cIntRetinaFactor());
|
|
_slideAnimation->setFinalImages(
|
|
direction,
|
|
std::move(wasCache),
|
|
std::move(nowCache),
|
|
slidingRect,
|
|
wasSectionIcons);
|
|
_slideAnimation->setCornerMasks(
|
|
Images::CornersMask(st::emojiPanRadius));
|
|
_slideAnimation->start();
|
|
|
|
hideForSliding();
|
|
|
|
getTab(wasIndex)->widget()->hideFinished();
|
|
|
|
_a_slide.start(
|
|
[=] { update(); },
|
|
0.,
|
|
1.,
|
|
st::emojiPanSlideDuration,
|
|
anim::linear);
|
|
update();
|
|
|
|
if (full()) {
|
|
session().settings().setSelectorTab(_currentTabType);
|
|
session().saveSettingsDelayed();
|
|
}
|
|
}
|
|
|
|
not_null<EmojiListWidget*> TabbedSelector::emoji() const {
|
|
Expects(hasEmojiTab());
|
|
|
|
return static_cast<EmojiListWidget*>(
|
|
getTab(indexByType(SelectorTab::Emoji))->widget());
|
|
}
|
|
|
|
not_null<StickersListWidget*> TabbedSelector::stickers() const {
|
|
Expects(hasStickersTab());
|
|
|
|
return static_cast<StickersListWidget*>(
|
|
getTab(indexByType(SelectorTab::Stickers))->widget());
|
|
}
|
|
|
|
not_null<GifsListWidget*> TabbedSelector::gifs() const {
|
|
Expects(hasGifsTab());
|
|
|
|
return static_cast<GifsListWidget*>(
|
|
getTab(indexByType(SelectorTab::Gifs))->widget());
|
|
}
|
|
|
|
not_null<StickersListWidget*> TabbedSelector::masks() const {
|
|
Expects(hasMasksTab());
|
|
|
|
return static_cast<StickersListWidget*>(
|
|
getTab(indexByType(SelectorTab::Masks))->widget());
|
|
}
|
|
|
|
void TabbedSelector::setWidgetToScrollArea() {
|
|
auto inner = _scroll->setOwnedWidget(currentTab()->takeWidget());
|
|
auto innerWidth = _scroll->width() - st::emojiScroll.width;
|
|
auto scrollHeight = _scroll->height();
|
|
inner->setMinimalHeight(innerWidth, scrollHeight);
|
|
inner->moveToLeft(0, 0);
|
|
inner->show();
|
|
|
|
_scroll->disableScroll(false);
|
|
scrollToY(currentTab()->getScrollTop());
|
|
handleScroll();
|
|
}
|
|
|
|
void TabbedSelector::scrollToY(int y) {
|
|
_scroll->scrollToY(y);
|
|
|
|
// Qt render glitch workaround, shadow sometimes disappears if we just scroll to y.
|
|
if (_topShadow) {
|
|
_topShadow->update();
|
|
}
|
|
}
|
|
|
|
void TabbedSelector::showMenuWithType(SendMenu::Type type) {
|
|
_menu = currentTab()->widget()->fillContextMenu(type);
|
|
if (_menu && !_menu->empty()) {
|
|
_menu->popup(QCursor::pos());
|
|
}
|
|
}
|
|
|
|
void TabbedSelector::setDropDown(bool dropDown) {
|
|
if (_dropDown == dropDown) {
|
|
return;
|
|
}
|
|
_dropDown = dropDown;
|
|
updateFooterGeometry();
|
|
updateScrollGeometry(size());
|
|
}
|
|
|
|
rpl::producer<> TabbedSelector::contextMenuRequested() const {
|
|
return events(
|
|
) | rpl::filter([=](not_null<QEvent*> e) {
|
|
return e->type() == QEvent::ContextMenu;
|
|
}) | rpl::to_empty;
|
|
}
|
|
|
|
SelectorTab TabbedSelector::typeByIndex(int index) const {
|
|
for (const auto &tab : _tabs) {
|
|
if (tab.index() == index) {
|
|
return tab.type();
|
|
}
|
|
}
|
|
Unexpected("Type in TabbedSelector::typeByIndex.");
|
|
}
|
|
|
|
int TabbedSelector::indexByType(SelectorTab type) const {
|
|
for (const auto &tab : _tabs) {
|
|
if (tab.type() == type) {
|
|
return tab.index();
|
|
}
|
|
}
|
|
Unexpected("Index in TabbedSelector::indexByType.");
|
|
}
|
|
|
|
not_null<TabbedSelector::Tab*> TabbedSelector::getTab(int index) {
|
|
return &(_tabs[index]);
|
|
}
|
|
|
|
not_null<const TabbedSelector::Tab*> TabbedSelector::getTab(int index) const {
|
|
return &_tabs[index];
|
|
}
|
|
|
|
not_null<TabbedSelector::Tab*> TabbedSelector::currentTab() {
|
|
return &_tabs[indexByType(_currentTabType)];
|
|
}
|
|
|
|
not_null<const TabbedSelector::Tab*> TabbedSelector::currentTab() const {
|
|
return &_tabs[indexByType(_currentTabType)];
|
|
}
|
|
|
|
TabbedSelector::Inner::Inner(
|
|
QWidget *parent,
|
|
std::shared_ptr<Show> show,
|
|
PauseReason level)
|
|
: Inner(
|
|
parent,
|
|
st::defaultEmojiPan,
|
|
show,
|
|
[show, level] { return show->paused(level); }) {
|
|
}
|
|
|
|
TabbedSelector::Inner::Inner(
|
|
QWidget *parent,
|
|
const style::EmojiPan &st,
|
|
std::shared_ptr<Show> show,
|
|
Fn<bool()> paused)
|
|
: RpWidget(parent)
|
|
, _st(st)
|
|
, _show(std::move(show))
|
|
, _session(&_show->session())
|
|
, _paused(paused) {
|
|
}
|
|
|
|
rpl::producer<int> TabbedSelector::Inner::scrollToRequests() const {
|
|
return _scrollToRequests.events();
|
|
}
|
|
|
|
rpl::producer<bool> TabbedSelector::Inner::disableScrollRequests() const {
|
|
return _disableScrollRequests.events();
|
|
}
|
|
|
|
void TabbedSelector::Inner::scrollTo(int y) {
|
|
_scrollToRequests.fire_copy(y);
|
|
}
|
|
|
|
void TabbedSelector::Inner::disableScroll(bool disabled) {
|
|
_disableScrollRequests.fire_copy(disabled);
|
|
}
|
|
|
|
void TabbedSelector::Inner::checkHideWithBox(
|
|
object_ptr<Ui::BoxContent> box) {
|
|
const auto raw = QPointer<Ui::BoxContent>(box.data());
|
|
_show->showBox(std::move(box));
|
|
if (!raw) {
|
|
return;
|
|
}
|
|
_preventHideWithBox = true;
|
|
connect(raw, &QObject::destroyed, this, [=] {
|
|
_preventHideWithBox = false;
|
|
_checkForHide.fire({});
|
|
});
|
|
}
|
|
|
|
void TabbedSelector::Inner::paintEmptySearchResults(
|
|
Painter &p,
|
|
const style::icon &icon,
|
|
const QString &text) const {
|
|
const auto iconLeft = (width() - icon.width()) / 2;
|
|
const auto iconTop = std::max(
|
|
(height() / 3) - (icon.height() / 2),
|
|
st::normalFont->height);
|
|
icon.paint(p, iconLeft, iconTop, width());
|
|
|
|
const auto textWidth = st::normalFont->width(text);
|
|
const auto textTop = std::min(
|
|
iconTop + icon.height() - st::normalFont->height,
|
|
height() - 2 * st::normalFont->height);
|
|
p.setFont(st::normalFont);
|
|
p.setPen(_st.tabs.labelFg);
|
|
p.drawTextLeft(
|
|
(width() - textWidth) / 2,
|
|
textTop,
|
|
width(),
|
|
text,
|
|
textWidth);
|
|
}
|
|
|
|
void TabbedSelector::Inner::visibleTopBottomUpdated(
|
|
int visibleTop,
|
|
int visibleBottom) {
|
|
_visibleTop = visibleTop;
|
|
_visibleBottom = visibleBottom;
|
|
}
|
|
|
|
void TabbedSelector::Inner::setMinimalHeight(
|
|
int newWidth,
|
|
int newMinimalHeight) {
|
|
if (_minimalHeight != newMinimalHeight) {
|
|
_minimalHeight = newMinimalHeight;
|
|
resizeToWidth(newWidth);
|
|
} else if (newWidth != width()) {
|
|
resizeToWidth(newWidth);
|
|
}
|
|
}
|
|
|
|
int TabbedSelector::Inner::resizeGetHeight(int newWidth) {
|
|
auto result = std::max(
|
|
countDesiredHeight(newWidth),
|
|
minimalHeight());
|
|
if (result != height()) {
|
|
update();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
int TabbedSelector::Inner::minimalHeight() const {
|
|
return (_minimalHeight > 0)
|
|
? _minimalHeight
|
|
: defaultMinimalHeight();
|
|
}
|
|
|
|
int TabbedSelector::Inner::defaultMinimalHeight() const {
|
|
return st::emojiPanMaxHeight - _st.footer;
|
|
}
|
|
|
|
void TabbedSelector::Inner::hideFinished() {
|
|
processHideFinished();
|
|
if (auto footer = getFooter()) {
|
|
footer->processHideFinished();
|
|
}
|
|
}
|
|
|
|
void TabbedSelector::Inner::panelHideFinished() {
|
|
hideFinished();
|
|
processPanelHideFinished();
|
|
if (auto footer = getFooter()) {
|
|
footer->processPanelHideFinished();
|
|
}
|
|
}
|
|
|
|
TabbedSelector::InnerFooter::InnerFooter(
|
|
QWidget *parent,
|
|
const style::EmojiPan &st)
|
|
: RpWidget(parent)
|
|
, _st(st) {
|
|
resize(st::emojiPanWidth, _st.footer);
|
|
}
|
|
|
|
const style::EmojiPan &TabbedSelector::InnerFooter::st() const {
|
|
return _st;
|
|
}
|
|
|
|
} // namespace ChatHelpers
|