1 /*
2 This file is part of Telegram Desktop,
3 the official desktop application for the Telegram messaging service.
4
5 For license and copyright information please follow this link:
6 https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
7 */
8 #include "chat_helpers/tabbed_selector.h"
9
10 #include "chat_helpers/emoji_list_widget.h"
11 #include "chat_helpers/stickers_list_widget.h"
12 #include "chat_helpers/gifs_list_widget.h"
13 #include "chat_helpers/send_context_menu.h"
14 #include "ui/widgets/buttons.h"
15 #include "ui/widgets/labels.h"
16 #include "ui/widgets/shadow.h"
17 #include "ui/widgets/discrete_sliders.h"
18 #include "ui/widgets/popup_menu.h"
19 #include "ui/widgets/scroll_area.h"
20 #include "ui/image/image_prepare.h"
21 #include "ui/cached_round_corners.h"
22 #include "window/window_session_controller.h"
23 #include "main/main_session.h"
24 #include "main/main_session_settings.h"
25 #include "storage/localstorage.h"
26 #include "data/data_channel.h"
27 #include "data/data_session.h"
28 #include "data/data_changes.h"
29 #include "data/stickers/data_stickers.h"
30 #include "lang/lang_keys.h"
31 #include "mainwindow.h"
32 #include "apiwrap.h"
33 #include "styles/style_chat_helpers.h"
34
35 namespace ChatHelpers {
36
37 class TabbedSelector::SlideAnimation : public Ui::RoundShadowAnimation {
38 public:
39 enum class Direction {
40 LeftToRight,
41 RightToLeft,
42 };
43 void setFinalImages(Direction direction, QImage &&left, QImage &&right, QRect inner, bool wasSectionIcons);
44
45 void start();
46 void paintFrame(QPainter &p, float64 dt, float64 opacity);
47
48 private:
49 Direction _direction = Direction::LeftToRight;
50 QPixmap _leftImage, _rightImage;
51 int _width = 0;
52 int _height = 0;
53 int _innerLeft = 0;
54 int _innerTop = 0;
55 int _innerRight = 0;
56 int _innerBottom = 0;
57 int _innerWidth = 0;
58 int _innerHeight = 0;
59
60 int _painterInnerLeft = 0;
61 int _painterInnerTop = 0;
62 int _painterInnerWidth = 0;
63 int _painterInnerBottom = 0;
64 int _painterCategoriesTop = 0;
65 int _painterInnerHeight = 0;
66 int _painterInnerRight = 0;
67
68 int _frameIntsPerLineAdd = 0;
69 bool _wasSectionIcons = false;
70
71 };
72
setFinalImages(Direction direction,QImage && left,QImage && right,QRect inner,bool wasSectionIcons)73 void TabbedSelector::SlideAnimation::setFinalImages(Direction direction, QImage &&left, QImage &&right, QRect inner, bool wasSectionIcons) {
74 Expects(!started());
75 _direction = direction;
76 _leftImage = QPixmap::fromImage(std::move(left).convertToFormat(QImage::Format_ARGB32_Premultiplied), Qt::ColorOnly);
77 _rightImage = QPixmap::fromImage(std::move(right).convertToFormat(QImage::Format_ARGB32_Premultiplied), Qt::ColorOnly);
78
79 Assert(!_leftImage.isNull());
80 Assert(!_rightImage.isNull());
81 _width = _leftImage.width();
82 _height = _rightImage.height();
83 Assert(!(_width % cIntRetinaFactor()));
84 Assert(!(_height % cIntRetinaFactor()));
85 Assert(_leftImage.devicePixelRatio() == _rightImage.devicePixelRatio());
86 Assert(_rightImage.width() == _width);
87 Assert(_rightImage.height() == _height);
88 Assert(QRect(0, 0, _width, _height).contains(inner));
89 _innerLeft = inner.x();
90 _innerTop = inner.y();
91 _innerWidth = inner.width();
92 _innerHeight = inner.height();
93 Assert(!(_innerLeft % cIntRetinaFactor()));
94 Assert(!(_innerTop % cIntRetinaFactor()));
95 Assert(!(_innerWidth % cIntRetinaFactor()));
96 Assert(!(_innerHeight % cIntRetinaFactor()));
97 _innerRight = _innerLeft + _innerWidth;
98 _innerBottom = _innerTop + _innerHeight;
99
100 _painterInnerLeft = _innerLeft / cIntRetinaFactor();
101 _painterInnerTop = _innerTop / cIntRetinaFactor();
102 _painterInnerRight = _innerRight / cIntRetinaFactor();
103 _painterInnerBottom = _innerBottom / cIntRetinaFactor();
104 _painterInnerWidth = _innerWidth / cIntRetinaFactor();
105 _painterInnerHeight = _innerHeight / cIntRetinaFactor();
106 _painterCategoriesTop = _painterInnerBottom - st::emojiFooterHeight;
107
108 _wasSectionIcons = wasSectionIcons;
109 }
110
start()111 void TabbedSelector::SlideAnimation::start() {
112 Assert(!_leftImage.isNull());
113 Assert(!_rightImage.isNull());
114 RoundShadowAnimation::start(_width, _height, _leftImage.devicePixelRatio());
115 auto checkCorner = [this](const Corner &corner) {
116 if (!corner.valid()) return;
117 Assert(corner.width <= _innerWidth);
118 Assert(corner.height <= _innerHeight);
119 };
120 checkCorner(_topLeft);
121 checkCorner(_topRight);
122 checkCorner(_bottomLeft);
123 checkCorner(_bottomRight);
124 _frameIntsPerLineAdd = (_width - _innerWidth) + _frameIntsPerLineAdded;
125 }
126
paintFrame(QPainter & p,float64 dt,float64 opacity)127 void TabbedSelector::SlideAnimation::paintFrame(QPainter &p, float64 dt, float64 opacity) {
128 Expects(started());
129 Expects(dt >= 0.);
130
131 _frameAlpha = anim::interpolate(1, 256, opacity);
132
133 auto leftToRight = (_direction == Direction::LeftToRight);
134
135 auto easeOut = anim::easeOutCirc(1., dt);
136 auto easeIn = anim::easeInCirc(1., dt);
137
138 auto arrivingCoord = anim::interpolate(_innerWidth, 0, easeOut);
139 auto departingCoord = anim::interpolate(0, _innerWidth, easeIn);
140 if (auto decrease = (arrivingCoord % cIntRetinaFactor())) {
141 arrivingCoord -= decrease;
142 }
143 if (auto decrease = (departingCoord % cIntRetinaFactor())) {
144 departingCoord -= decrease;
145 }
146 auto arrivingAlpha = easeIn;
147 auto departingAlpha = 1. - easeOut;
148 auto leftCoord = (leftToRight ? arrivingCoord : departingCoord) * -1;
149 auto leftAlpha = (leftToRight ? arrivingAlpha : departingAlpha);
150 auto rightCoord = (leftToRight ? departingCoord : arrivingCoord);
151 auto rightAlpha = (leftToRight ? departingAlpha : arrivingAlpha);
152
153 // _innerLeft ..(left).. leftTo ..(both).. bothTo ..(none).. noneTo ..(right).. _innerRight
154 auto leftTo = _innerLeft
155 + std::clamp(_innerWidth + leftCoord, 0, _innerWidth);
156 auto rightFrom = _innerLeft + std::clamp(rightCoord, 0, _innerWidth);
157 auto painterRightFrom = rightFrom / cIntRetinaFactor();
158 if (opacity < 1.) {
159 _frame.fill(Qt::transparent);
160 }
161 {
162 Painter p(&_frame);
163 p.setOpacity(opacity);
164 p.fillRect(_painterInnerLeft, _painterInnerTop, _painterInnerWidth, _painterCategoriesTop - _painterInnerTop, st::emojiPanBg);
165 p.fillRect(_painterInnerLeft, _painterCategoriesTop, _painterInnerWidth, _painterInnerBottom - _painterCategoriesTop, _wasSectionIcons ? st::emojiPanCategories : st::emojiPanBg);
166 p.setCompositionMode(QPainter::CompositionMode_SourceOver);
167 if (leftTo > _innerLeft) {
168 p.setOpacity(opacity * leftAlpha);
169 p.drawPixmap(_painterInnerLeft, _painterInnerTop, _leftImage, _innerLeft - leftCoord, _innerTop, leftTo - _innerLeft, _innerHeight);
170 }
171 if (rightFrom < _innerRight) {
172 p.setOpacity(opacity * rightAlpha);
173 p.drawPixmap(painterRightFrom, _painterInnerTop, _rightImage, _innerLeft, _innerTop, _innerRight - rightFrom, _innerHeight);
174 }
175 }
176
177 // Draw corners
178 //paintCorner(_topLeft, _innerLeft, _innerTop);
179 //paintCorner(_topRight, _innerRight - _topRight.width, _innerTop);
180 paintCorner(_bottomLeft, _innerLeft, _innerBottom - _bottomLeft.height);
181 paintCorner(_bottomRight, _innerRight - _bottomRight.width, _innerBottom - _bottomRight.height);
182
183 // Draw shadow upon the transparent
184 auto outerLeft = _innerLeft;
185 auto outerTop = _innerTop;
186 auto outerRight = _innerRight;
187 auto outerBottom = _innerBottom;
188 if (_shadow.valid()) {
189 outerLeft -= _shadow.extend.left();
190 outerTop -= _shadow.extend.top();
191 outerRight += _shadow.extend.right();
192 outerBottom += _shadow.extend.bottom();
193 }
194 if (cIntRetinaFactor() > 1) {
195 if (auto skipLeft = (outerLeft % cIntRetinaFactor())) {
196 outerLeft -= skipLeft;
197 }
198 if (auto skipTop = (outerTop % cIntRetinaFactor())) {
199 outerTop -= skipTop;
200 }
201 if (auto skipRight = (outerRight % cIntRetinaFactor())) {
202 outerRight += (cIntRetinaFactor() - skipRight);
203 }
204 if (auto skipBottom = (outerBottom % cIntRetinaFactor())) {
205 outerBottom += (cIntRetinaFactor() - skipBottom);
206 }
207 }
208
209 if (opacity == 1.) {
210 // Fill above the frame top with transparent.
211 auto fillTopInts = (_frameInts + outerTop * _frameIntsPerLine + outerLeft);
212 auto fillWidth = (outerRight - outerLeft) * sizeof(uint32);
213 for (auto fillTop = _innerTop - outerTop; fillTop != 0; --fillTop) {
214 memset(fillTopInts, 0, fillWidth);
215 fillTopInts += _frameIntsPerLine;
216 }
217
218 // Fill to the left and to the right of the frame with transparent.
219 auto fillLeft = (_innerLeft - outerLeft) * sizeof(uint32);
220 auto fillRight = (outerRight - _innerRight) * sizeof(uint32);
221 if (fillLeft || fillRight) {
222 auto fillInts = _frameInts + _innerTop * _frameIntsPerLine;
223 for (auto y = _innerTop; y != _innerBottom; ++y) {
224 memset(fillInts + outerLeft, 0, fillLeft);
225 memset(fillInts + _innerRight, 0, fillRight);
226 fillInts += _frameIntsPerLine;
227 }
228 }
229
230 // Fill below the frame bottom with transparent.
231 auto fillBottomInts = (_frameInts + _innerBottom * _frameIntsPerLine + outerLeft);
232 for (auto fillBottom = outerBottom - _innerBottom; fillBottom != 0; --fillBottom) {
233 memset(fillBottomInts, 0, fillWidth);
234 fillBottomInts += _frameIntsPerLine;
235 }
236 }
237 if (_shadow.valid()) {
238 paintShadow(outerLeft, outerTop, outerRight, outerBottom);
239 }
240
241 // Debug
242 //auto frameInts = _frameInts;
243 //auto pattern = anim::shifted((static_cast<uint32>(0xFF) << 24) | (static_cast<uint32>(0xFF) << 16) | (static_cast<uint32>(0xFF) << 8) | static_cast<uint32>(0xFF));
244 //for (auto y = 0; y != _finalHeight; ++y) {
245 // for (auto x = 0; x != _finalWidth; ++x) {
246 // auto source = *frameInts;
247 // auto sourceAlpha = (source >> 24);
248 // *frameInts = anim::unshifted(anim::shifted(source) * 256 + pattern * (256 - sourceAlpha));
249 // ++frameInts;
250 // }
251 // frameInts += _frameIntsPerLineAdded;
252 //}
253
254 p.drawImage(outerLeft / cIntRetinaFactor(), outerTop / cIntRetinaFactor(), _frame, outerLeft, outerTop, outerRight - outerLeft, outerBottom - outerTop);
255 }
256
Tab(SelectorTab type,int index,object_ptr<Inner> widget)257 TabbedSelector::Tab::Tab(
258 SelectorTab type,
259 int index,
260 object_ptr<Inner> widget)
261 : _type(type)
262 , _index(index)
263 , _widget(std::move(widget))
264 , _weak(_widget)
265 , _footer(_widget ? _widget->createFooter() : nullptr) {
266 if (_footer) {
267 _footer->setParent(_widget->parentWidget());
268 }
269 }
270
takeWidget()271 object_ptr<TabbedSelector::Inner> TabbedSelector::Tab::takeWidget() {
272 return std::move(_widget);
273 }
274
returnWidget(object_ptr<Inner> widget)275 void TabbedSelector::Tab::returnWidget(object_ptr<Inner> widget) {
276 Expects(widget == _weak);
277
278 _widget = std::move(widget);
279 }
280
saveScrollTop()281 void TabbedSelector::Tab::saveScrollTop() {
282 Expects(widget() != nullptr);
283
284 _scrollTop = widget()->getVisibleTop();
285 }
286
TabbedSelector(QWidget * parent,not_null<Window::SessionController * > controller,Mode mode)287 TabbedSelector::TabbedSelector(
288 QWidget *parent,
289 not_null<Window::SessionController*> controller,
290 Mode mode)
291 : RpWidget(parent)
292 , _controller(controller)
293 , _mode(mode)
294 , _topShadow(full() ? object_ptr<Ui::PlainShadow>(this) : nullptr)
295 , _bottomShadow(this)
296 , _scroll(this, st::emojiScroll)
297 , _tabs([&] {
298 std::vector<Tab> tabs;
299 if (full()) {
300 tabs.reserve(3);
301 tabs.push_back(createTab(SelectorTab::Emoji, 0));
302 tabs.push_back(createTab(SelectorTab::Stickers, 1));
303 tabs.push_back(createTab(SelectorTab::Gifs, 2));
304 } else if (mediaEditor()) {
305 tabs.reserve(2);
306 tabs.push_back(createTab(SelectorTab::Stickers, 0));
307 tabs.push_back(createTab(SelectorTab::Masks, 1));
308 } else {
309 tabs.reserve(1);
310 tabs.push_back(createTab(SelectorTab::Emoji, 0));
311 }
312 return tabs;
313 }())
314 , _currentTabType(full()
315 ? session().settings().selectorTab()
316 : mediaEditor()
317 ? SelectorTab::Stickers
318 : SelectorTab::Emoji)
319 , _hasEmojiTab(ranges::contains(_tabs, SelectorTab::Emoji, &Tab::type))
320 , _hasStickersTab(ranges::contains(_tabs, SelectorTab::Stickers, &Tab::type))
321 , _hasGifsTab(ranges::contains(_tabs, SelectorTab::Gifs, &Tab::type))
322 , _hasMasksTab(ranges::contains(_tabs, SelectorTab::Masks, &Tab::type))
323 , _tabbed(_tabs.size() > 1) {
324 resize(st::emojiPanWidth, st::emojiPanMaxHeight);
325
326 for (auto &tab : _tabs) {
327 tab.footer()->hide();
328 tab.widget()->hide();
329 }
330 if (tabbed()) {
331 createTabsSlider();
332 }
333 setWidgetToScrollArea();
334
335 _bottomShadow->setGeometry(
336 0,
337 _scroll->y() + _scroll->height() - st::lineWidth,
338 width(),
339 st::lineWidth);
340
341 for (auto &tab : _tabs) {
342 const auto widget = tab.widget();
343
344 widget->scrollToRequests(
__anonb25f9b710302(int y) 345 ) | rpl::start_with_next([=, tab = &tab](int y) {
346 if (tab == currentTab()) {
347 scrollToY(y);
348 } else {
349 tab->saveScrollTop(y);
350 }
351 }, widget->lifetime());
352
353 widget->disableScrollRequests(
__anonb25f9b710402(bool disabled) 354 ) | rpl::start_with_next([=, tab = &tab](bool disabled) {
355 if (tab == currentTab()) {
356 _scroll->disableScroll(disabled);
357 }
358 }, widget->lifetime());
359 }
360
361 rpl::merge(
362 (hasStickersTab()
363 ? stickers()->scrollUpdated() | rpl::map_to(0)
364 : rpl::never<int>() | rpl::type_erased()),
365 _scroll->scrollTopChanges()
__anonb25f9b710502null366 ) | rpl::start_with_next([=] {
367 handleScroll();
368 }, lifetime());
369
370 if (_topShadow) {
371 _topShadow->raise();
372 }
373 _bottomShadow->raise();
374 if (_tabsSlider) {
375 _tabsSlider->raise();
376 }
377
378 if (hasStickersTab() || hasGifsTab()) {
379 session().changes().peerUpdates(
380 Data::PeerUpdate::Flag::Rights
__anonb25f9b710602(const Data::PeerUpdate &update) 381 ) | rpl::filter([=](const Data::PeerUpdate &update) {
382 return (update.peer.get() == _currentPeer);
383 }) | rpl::start_with_next([=] {
384 checkRestrictedPeer();
385 }, lifetime());
386 }
387
388 if (hasStickersTab()) {
389 session().data().stickers().stickerSetInstalled(
__anonb25f9b710802(uint64 setId) 390 ) | rpl::start_with_next([=](uint64 setId) {
391 _tabsSlider->setActiveSection(indexByType(SelectorTab::Stickers));
392 stickers()->showStickerSet(setId);
393 _showRequests.fire({});
394 }, lifetime());
395
396 session().data().stickers().updated(
__anonb25f9b710902null397 ) | rpl::start_with_next([=] {
398 refreshStickers();
399 }, lifetime());
400 refreshStickers();
401 }
402 //setAttribute(Qt::WA_AcceptTouchEvents);
403 setAttribute(Qt::WA_OpaquePaintEvent, false);
404 showAll();
405 hide();
406 }
407
408 TabbedSelector::~TabbedSelector() = default;
409
session() const410 Main::Session &TabbedSelector::session() const {
411 return _controller->session();
412 }
413
createTab(SelectorTab type,int index)414 TabbedSelector::Tab TabbedSelector::createTab(SelectorTab type, int index) {
415 auto createWidget = [&]() -> object_ptr<Inner> {
416 switch (type) {
417 case SelectorTab::Emoji:
418 return object_ptr<EmojiListWidget>(this, _controller);
419 case SelectorTab::Stickers:
420 return object_ptr<StickersListWidget>(this, _controller);
421 case SelectorTab::Gifs:
422 return object_ptr<GifsListWidget>(this, _controller);
423 case SelectorTab::Masks:
424 return object_ptr<StickersListWidget>(this, _controller, true);
425 }
426 Unexpected("Type in TabbedSelector::createTab.");
427 };
428 return Tab{ type, index, createWidget() };
429 }
430
full() const431 bool TabbedSelector::full() const {
432 return (_mode == Mode::Full);
433 }
434
mediaEditor() const435 bool TabbedSelector::mediaEditor() const {
436 return (_mode == Mode::MediaEditor);
437 }
438
tabbed() const439 bool TabbedSelector::tabbed() const {
440 return _tabbed;
441 }
442
hasEmojiTab() const443 bool TabbedSelector::hasEmojiTab() const {
444 return _hasEmojiTab;
445 }
446
hasStickersTab() const447 bool TabbedSelector::hasStickersTab() const {
448 return _hasStickersTab;
449 }
450
hasGifsTab() const451 bool TabbedSelector::hasGifsTab() const {
452 return _hasGifsTab;
453 }
454
hasMasksTab() const455 bool TabbedSelector::hasMasksTab() const {
456 return _hasMasksTab;
457 }
458
emojiChosen() const459 rpl::producer<EmojiPtr> TabbedSelector::emojiChosen() const {
460 return emoji()->chosen();
461 }
462
fileChosen() const463 rpl::producer<TabbedSelector::FileChosen> TabbedSelector::fileChosen() const {
464 auto never = rpl::never<TabbedSelector::FileChosen>(
465 ) | rpl::type_erased();
466 return rpl::merge(
467 hasStickersTab() ? stickers()->chosen() : never,
468 hasGifsTab() ? gifs()->fileChosen() : never,
469 hasMasksTab() ? masks()->chosen() : never);
470 }
471
photoChosen() const472 auto TabbedSelector::photoChosen() const
473 -> rpl::producer<TabbedSelector::PhotoChosen>{
474 return hasGifsTab() ? gifs()->photoChosen() : nullptr;
475 }
476
inlineResultChosen() const477 auto TabbedSelector::inlineResultChosen() const
478 -> rpl::producer<InlineChosen> {
479 return hasGifsTab() ? gifs()->inlineResultChosen() : nullptr;
480 }
481
choosingStickerUpdated() const482 auto TabbedSelector::choosingStickerUpdated() const
483 -> rpl::producer<TabbedSelector::Action>{
484 return hasStickersTab()
485 ? stickers()->choosingUpdated()
486 : rpl::never<Action>();
487 }
488
cancelled() const489 rpl::producer<> TabbedSelector::cancelled() const {
490 return hasGifsTab() ? gifs()->cancelRequests() : nullptr;
491 }
492
checkForHide() const493 rpl::producer<> TabbedSelector::checkForHide() const {
494 auto never = rpl::never<>();
495 return rpl::merge(
496 hasStickersTab() ? stickers()->checkForHide() : never,
497 hasMasksTab() ? masks()->checkForHide() : never);
498 }
499
slideFinished() const500 rpl::producer<> TabbedSelector::slideFinished() const {
501 return _slideFinished.events();
502 }
503
updateTabsSliderGeometry()504 void TabbedSelector::updateTabsSliderGeometry() {
505 if (!_tabsSlider) {
506 return;
507 }
508 const auto w = mediaEditor() && hasMasksTab() && masks()->mySetsEmpty()
509 ? width() / 2
510 : width();
511 _tabsSlider->resizeToWidth(w);
512 _tabsSlider->moveToLeft(0, 0);
513 }
514
resizeEvent(QResizeEvent * e)515 void TabbedSelector::resizeEvent(QResizeEvent *e) {
516 updateTabsSliderGeometry();
517 if (_topShadow && _tabsSlider) {
518 _topShadow->setGeometry(
519 _tabsSlider->x(),
520 _tabsSlider->bottomNoMargins() - st::lineWidth,
521 _tabsSlider->width(),
522 st::lineWidth);
523 }
524
525 auto scrollWidth = width() - st::roundRadiusSmall;
526 auto scrollHeight = height() - scrollTop() - marginBottom();
527 auto inner = currentTab()->widget();
528 auto innerWidth = scrollWidth - st::emojiScroll.width;
529 auto updateScrollGeometry = [&] {
530 _scroll->setGeometryToLeft(
531 st::roundRadiusSmall,
532 scrollTop(),
533 scrollWidth,
534 scrollHeight);
535 };
536 auto updateInnerGeometry = [&] {
537 auto scrollTop = _scroll->scrollTop();
538 auto scrollBottom = scrollTop + scrollHeight;
539 inner->setMinimalHeight(innerWidth, scrollHeight);
540 inner->setVisibleTopBottom(scrollTop, scrollBottom);
541 };
542 if (e->oldSize().height() > height()) {
543 updateScrollGeometry();
544 updateInnerGeometry();
545 } else {
546 updateInnerGeometry();
547 updateScrollGeometry();
548 }
549 _bottomShadow->setGeometry(
550 0,
551 _scroll->y() + _scroll->height() - st::lineWidth,
552 width(),
553 st::lineWidth);
554 updateRestrictedLabelGeometry();
555
556 _footerTop = height() - st::emojiFooterHeight;
557 for (auto &tab : _tabs) {
558 tab.footer()->resizeToWidth(width());
559 tab.footer()->moveToLeft(0, _footerTop);
560 }
561
562 update();
563 }
564
updateRestrictedLabelGeometry()565 void TabbedSelector::updateRestrictedLabelGeometry() {
566 if (!_restrictedLabel) {
567 return;
568 }
569
570 auto labelWidth = width() - st::stickerPanPadding * 2;
571 _restrictedLabel->resizeToWidth(labelWidth);
572 _restrictedLabel->moveToLeft(
573 (width() - _restrictedLabel->width()) / 2,
574 (height() / 3 - _restrictedLabel->height() / 2));
575 }
576
paintEvent(QPaintEvent * e)577 void TabbedSelector::paintEvent(QPaintEvent *e) {
578 Painter p(this);
579
580 auto switching = (_slideAnimation != nullptr);
581 if (switching) {
582 paintSlideFrame(p);
583 if (!_a_slide.animating()) {
584 _slideAnimation.reset();
585 afterShown();
586 _slideFinished.fire({});
587 }
588 } else {
589 paintContent(p);
590 }
591 }
592
paintSlideFrame(Painter & p)593 void TabbedSelector::paintSlideFrame(Painter &p) {
594 if (_roundRadius > 0) {
595 const auto topPart = QRect(
596 0,
597 0,
598 width(),
599 _tabsSlider
600 ? _tabsSlider->height() + _roundRadius
601 : 3 * _roundRadius);
602 Ui::FillRoundRect(
603 p,
604 topPart,
605 st::emojiPanBg,
606 ImageRoundRadius::Small,
607 tabbed()
608 ? RectPart::FullTop | RectPart::NoTopBottom
609 : RectPart::FullTop);
610 } else if (_tabsSlider) {
611 p.fillRect(0, 0, width(), _tabsSlider->height(), st::emojiPanBg);
612 }
613 auto slideDt = _a_slide.value(1.);
614 _slideAnimation->paintFrame(p, slideDt, 1.);
615 }
616
paintContent(Painter & p)617 void TabbedSelector::paintContent(Painter &p) {
618 auto &bottomBg = hasSectionIcons()
619 ? st::emojiPanCategories
620 : st::emojiPanBg;
621 if (_roundRadius > 0) {
622 const auto topPart = QRect(
623 0,
624 0,
625 width(),
626 _tabsSlider
627 ? _tabsSlider->height() + _roundRadius
628 : 3 * _roundRadius);
629 Ui::FillRoundRect(
630 p,
631 topPart,
632 st::emojiPanBg,
633 ImageRoundRadius::Small,
634 tabbed()
635 ? RectPart::FullTop | RectPart::NoTopBottom
636 : RectPart::FullTop);
637
638 const auto bottomPart = QRect(
639 0,
640 _footerTop - _roundRadius,
641 width(),
642 st::emojiFooterHeight + _roundRadius);
643 Ui::FillRoundRect(
644 p,
645 bottomPart,
646 bottomBg,
647 ImageRoundRadius::Small,
648 RectPart::NoTopBottom | RectPart::FullBottom);
649 } else {
650 if (_tabsSlider) {
651 p.fillRect(0, 0, width(), _tabsSlider->height(), st::emojiPanBg);
652 }
653 p.fillRect(0, _footerTop, width(), st::emojiFooterHeight, bottomBg);
654 }
655
656 auto sidesTop = marginTop();
657 auto sidesHeight = height() - sidesTop - marginBottom();
658 if (_restrictedLabel) {
659 p.fillRect(0, sidesTop, width(), sidesHeight, st::emojiPanBg);
660 } else {
661 p.fillRect(
662 myrtlrect(
663 width() - st::emojiScroll.width,
664 sidesTop,
665 st::emojiScroll.width,
666 sidesHeight),
667 st::emojiPanBg);
668 p.fillRect(
669 myrtlrect(0, sidesTop, st::roundRadiusSmall, sidesHeight),
670 st::emojiPanBg);
671 }
672 }
673
marginTop() const674 int TabbedSelector::marginTop() const {
675 return _tabsSlider
676 ? (_tabsSlider->height() - st::lineWidth)
677 : _roundRadius;
678 }
679
scrollTop() const680 int TabbedSelector::scrollTop() const {
681 return tabbed() ? marginTop() : 0;
682 }
683
marginBottom() const684 int TabbedSelector::marginBottom() const {
685 return st::emojiFooterHeight;
686 }
687
refreshStickers()688 void TabbedSelector::refreshStickers() {
689 if (hasStickersTab()) {
690 stickers()->refreshStickers();
691 if (isHidden() || _currentTabType != SelectorTab::Stickers) {
692 stickers()->preloadImages();
693 }
694 }
695 if (hasMasksTab()) {
696 const auto masksList = masks();
697 masksList->refreshStickers();
698 if (isHidden() || _currentTabType != SelectorTab::Masks) {
699 masksList->preloadImages();
700 }
701
702 fillTabsSliderSections();
703 updateTabsSliderGeometry();
704 if (hasStickersTab() && masksList->mySetsEmpty()) {
705 _tabsSlider->setActiveSection(indexByType(SelectorTab::Stickers));
706 }
707 }
708 }
709
preventAutoHide() const710 bool TabbedSelector::preventAutoHide() const {
711 return (hasStickersTab() ? stickers()->preventAutoHide() : false)
712 || (hasMasksTab() ? masks()->preventAutoHide() : false)
713 || hasMenu();
714 }
715
hasMenu() const716 bool TabbedSelector::hasMenu() const {
717 return (_menu && !_menu->empty());
718 }
719
grabForAnimation()720 QImage TabbedSelector::grabForAnimation() {
721 auto slideAnimationData = base::take(_slideAnimation);
722 auto slideAnimation = base::take(_a_slide);
723
724 showAll();
725 if (_topShadow) {
726 _topShadow->hide();
727 }
728 if (_tabsSlider) {
729 _tabsSlider->hide();
730 }
731 Ui::SendPendingMoveResizeEvents(this);
732
733 auto result = QImage(
734 size() * cIntRetinaFactor(),
735 QImage::Format_ARGB32_Premultiplied);
736 result.setDevicePixelRatio(cRetinaFactor());
737 result.fill(Qt::transparent);
738 render(&result);
739
740 _a_slide = base::take(slideAnimation);
741 _slideAnimation = base::take(slideAnimationData);
742
743 return result;
744 }
745
floatPlayerHandleWheelEvent(QEvent * e)746 bool TabbedSelector::floatPlayerHandleWheelEvent(QEvent *e) {
747 return _scroll->viewportEvent(e);
748 }
749
floatPlayerAvailableRect() const750 QRect TabbedSelector::floatPlayerAvailableRect() const {
751 return mapToGlobal(_scroll->geometry());
752 }
753
hideFinished()754 void TabbedSelector::hideFinished() {
755 for (auto &tab : _tabs) {
756 tab.widget()->panelHideFinished();
757 }
758 _a_slide.stop();
759 _slideAnimation.reset();
760 }
761
showStarted()762 void TabbedSelector::showStarted() {
763 if (hasStickersTab()) {
764 session().api().updateStickers();
765 }
766 if (hasMasksTab()) {
767 session().api().updateMasks();
768 }
769 currentTab()->widget()->refreshRecent();
770 currentTab()->widget()->preloadImages();
771 _a_slide.stop();
772 _slideAnimation.reset();
773 showAll();
774 }
775
beforeHiding()776 void TabbedSelector::beforeHiding() {
777 if (!_scroll->isHidden()) {
778 currentTab()->widget()->beforeHiding();
779 if (_beforeHidingCallback) {
780 _beforeHidingCallback(_currentTabType);
781 }
782 }
783 }
784
afterShown()785 void TabbedSelector::afterShown() {
786 if (!_a_slide.animating()) {
787 showAll();
788 currentTab()->widget()->afterShown();
789 if (_afterShownCallback) {
790 _afterShownCallback(_currentTabType);
791 }
792 }
793 }
794
setCurrentPeer(PeerData * peer)795 void TabbedSelector::setCurrentPeer(PeerData *peer) {
796 if (hasGifsTab()) {
797 gifs()->setInlineQueryPeer(peer);
798 }
799 _currentPeer = peer;
800 checkRestrictedPeer();
801 if (hasStickersTab()) {
802 stickers()->showMegagroupSet(peer ? peer->asMegagroup() : nullptr);
803 }
804 }
805
checkRestrictedPeer()806 void TabbedSelector::checkRestrictedPeer() {
807 if (_currentPeer) {
808 const auto error = (_currentTabType == SelectorTab::Stickers)
809 ? Data::RestrictionError(
810 _currentPeer,
811 ChatRestriction::SendStickers)
812 : (_currentTabType == SelectorTab::Gifs)
813 ? Data::RestrictionError(
814 _currentPeer,
815 ChatRestriction::SendGifs)
816 : std::nullopt;
817 if (error) {
818 if (!_restrictedLabel) {
819 _restrictedLabel.create(
820 this,
821 *error,
822 st::stickersRestrictedLabel);
823 _restrictedLabel->show();
824 updateRestrictedLabelGeometry();
825 currentTab()->footer()->hide();
826 _scroll->hide();
827 _bottomShadow->hide();
828 update();
829 }
830 return;
831 }
832 }
833 if (_restrictedLabel) {
834 _restrictedLabel.destroy();
835 if (!_a_slide.animating()) {
836 currentTab()->footer()->show();
837 _scroll->show();
838 _bottomShadow->setVisible(_currentTabType == SelectorTab::Gifs);
839 update();
840 }
841 }
842 }
843
isRestrictedView()844 bool TabbedSelector::isRestrictedView() {
845 checkRestrictedPeer();
846 return (_restrictedLabel != nullptr);
847 }
848
showAll()849 void TabbedSelector::showAll() {
850 if (isRestrictedView()) {
851 _restrictedLabel->show();
852 } else {
853 currentTab()->footer()->show();
854 _scroll->show();
855 _bottomShadow->setVisible(_currentTabType == SelectorTab::Gifs);
856 }
857 if (_topShadow) {
858 _topShadow->show();
859 }
860 if (_tabsSlider) {
861 _tabsSlider->show();
862 }
863 }
864
hideForSliding()865 void TabbedSelector::hideForSliding() {
866 hideChildren();
867 if (_topShadow) {
868 _topShadow->show();
869 }
870 if (_tabsSlider) {
871 _tabsSlider->show();
872 }
873 currentTab()->widget()->clearSelection();
874 }
875
handleScroll()876 void TabbedSelector::handleScroll() {
877 auto scrollTop = _scroll->scrollTop();
878 auto scrollBottom = scrollTop + _scroll->height();
879 currentTab()->widget()->setVisibleTopBottom(scrollTop, scrollBottom);
880 }
881
setRoundRadius(int radius)882 void TabbedSelector::setRoundRadius(int radius) {
883 _roundRadius = radius;
884 if (_tabsSlider) {
885 _tabsSlider->setRippleTopRoundRadius(_roundRadius);
886 }
887 }
888
createTabsSlider()889 void TabbedSelector::createTabsSlider() {
890 _tabsSlider.create(this, st::emojiTabs);
891
892 fillTabsSliderSections();
893
894 _tabsSlider->setActiveSectionFast(indexByType(_currentTabType));
895 _tabsSlider->sectionActivated(
896 ) | rpl::start_with_next([=] {
897 switchTab();
898 }, lifetime());
899 }
900
fillTabsSliderSections()901 void TabbedSelector::fillTabsSliderSections() {
902 if (!_tabsSlider) {
903 return;
904 }
905
906 const auto sections = ranges::views::all(
907 _tabs
908 ) | ranges::views::filter([&](const Tab &tab) {
909 return (tab.type() == SelectorTab::Masks)
910 ? !masks()->mySetsEmpty()
911 : true;
912 }) | ranges::views::transform([&](const Tab &tab) {
913 return [&] {
914 switch (tab.type()) {
915 case SelectorTab::Emoji:
916 return tr::lng_switch_emoji;
917 case SelectorTab::Stickers:
918 return tr::lng_switch_stickers;
919 case SelectorTab::Gifs:
920 return tr::lng_switch_gifs;
921 case SelectorTab::Masks:
922 return tr::lng_switch_masks;
923 }
924 Unexpected("SelectorTab value in fillTabsSliderSections.");
925 }()(tr::now).toUpper();
926 }) | ranges::to_vector;
927 _tabsSlider->setSections(sections);
928 }
929
hasSectionIcons() const930 bool TabbedSelector::hasSectionIcons() const {
931 return (_currentTabType != SelectorTab::Gifs) && !_restrictedLabel;
932 }
933
switchTab()934 void TabbedSelector::switchTab() {
935 Expects(tabbed());
936
937 const auto tab = _tabsSlider->activeSection();
938 Assert(tab >= 0 && tab < _tabs.size());
939 const auto newTabType = typeByIndex(tab);
940 if (_currentTabType == newTabType) {
941 _scroll->scrollToY(0);
942 return;
943 }
944
945 const auto wasSectionIcons = hasSectionIcons();
946 const auto wasIndex = indexByType(_currentTabType);
947 currentTab()->saveScrollTop();
948
949 beforeHiding();
950
951 auto wasCache = grabForAnimation();
952
953 auto widget = _scroll->takeWidget<Inner>();
954 widget->setParent(this);
955 widget->hide();
956 currentTab()->footer()->hide();
957 currentTab()->returnWidget(std::move(widget));
958
959 _currentTabType = newTabType;
960 _restrictedLabel.destroy();
961 checkRestrictedPeer();
962
963 currentTab()->widget()->refreshRecent();
964 currentTab()->widget()->preloadImages();
965 setWidgetToScrollArea();
966
967 auto nowCache = grabForAnimation();
968
969 auto direction = (wasIndex > indexByType(_currentTabType))
970 ? SlideAnimation::Direction::LeftToRight
971 : SlideAnimation::Direction::RightToLeft;
972 if (direction == SlideAnimation::Direction::LeftToRight) {
973 std::swap(wasCache, nowCache);
974 }
975 _slideAnimation = std::make_unique<SlideAnimation>();
976 const auto slidingRect = QRect(
977 0,
978 _scroll->y() * cIntRetinaFactor(),
979 width() * cIntRetinaFactor(),
980 (height() - _scroll->y()) * cIntRetinaFactor());
981 _slideAnimation->setFinalImages(
982 direction,
983 std::move(wasCache),
984 std::move(nowCache),
985 slidingRect,
986 wasSectionIcons);
987 _slideAnimation->setCornerMasks(
988 Images::CornersMask(ImageRoundRadius::Small));
989 _slideAnimation->start();
990
991 hideForSliding();
992
993 getTab(wasIndex)->widget()->hideFinished();
994
995 _a_slide.start(
996 [=] { update(); },
997 0.,
998 1.,
999 st::emojiPanSlideDuration,
1000 anim::linear);
1001 update();
1002
1003 if (full()) {
1004 session().settings().setSelectorTab(_currentTabType);
1005 session().saveSettingsDelayed();
1006 }
1007 }
1008
emoji() const1009 not_null<EmojiListWidget*> TabbedSelector::emoji() const {
1010 Expects(hasEmojiTab());
1011
1012 return static_cast<EmojiListWidget*>(
1013 getTab(indexByType(SelectorTab::Emoji))->widget());
1014 }
1015
stickers() const1016 not_null<StickersListWidget*> TabbedSelector::stickers() const {
1017 Expects(hasStickersTab());
1018
1019 return static_cast<StickersListWidget*>(
1020 getTab(indexByType(SelectorTab::Stickers))->widget());
1021 }
1022
gifs() const1023 not_null<GifsListWidget*> TabbedSelector::gifs() const {
1024 Expects(hasGifsTab());
1025
1026 return static_cast<GifsListWidget*>(
1027 getTab(indexByType(SelectorTab::Gifs))->widget());
1028 }
1029
masks() const1030 not_null<StickersListWidget*> TabbedSelector::masks() const {
1031 Expects(hasMasksTab());
1032
1033 return static_cast<StickersListWidget*>(
1034 getTab(indexByType(SelectorTab::Masks))->widget());
1035 }
1036
setWidgetToScrollArea()1037 void TabbedSelector::setWidgetToScrollArea() {
1038 auto inner = _scroll->setOwnedWidget(currentTab()->takeWidget());
1039 auto innerWidth = _scroll->width() - st::emojiScroll.width;
1040 auto scrollHeight = _scroll->height();
1041 inner->setMinimalHeight(innerWidth, scrollHeight);
1042 inner->moveToLeft(0, 0);
1043 inner->show();
1044
1045 _scroll->disableScroll(false);
1046 scrollToY(currentTab()->getScrollTop());
1047 handleScroll();
1048 }
1049
scrollToY(int y)1050 void TabbedSelector::scrollToY(int y) {
1051 _scroll->scrollToY(y);
1052
1053 // Qt render glitch workaround, shadow sometimes disappears if we just scroll to y.
1054 if (_topShadow) {
1055 _topShadow->update();
1056 }
1057 }
1058
showMenuWithType(SendMenu::Type type)1059 void TabbedSelector::showMenuWithType(SendMenu::Type type) {
1060 _menu = base::make_unique_q<Ui::PopupMenu>(this);
1061 currentTab()->widget()->fillContextMenu(_menu, type);
1062
1063 if (!_menu->empty()) {
1064 _menu->popup(QCursor::pos());
1065 }
1066 }
1067
contextMenuRequested() const1068 rpl::producer<> TabbedSelector::contextMenuRequested() const {
1069 return events(
1070 ) | rpl::filter([=](not_null<QEvent*> e) {
1071 return e->type() == QEvent::ContextMenu;
1072 }) | rpl::to_empty;
1073 }
1074
typeByIndex(int index) const1075 SelectorTab TabbedSelector::typeByIndex(int index) const {
1076 for (const auto &tab : _tabs) {
1077 if (tab.index() == index) {
1078 return tab.type();
1079 }
1080 }
1081 Unexpected("Type in TabbedSelector::typeByIndex.");
1082 }
1083
indexByType(SelectorTab type) const1084 int TabbedSelector::indexByType(SelectorTab type) const {
1085 for (const auto &tab : _tabs) {
1086 if (tab.type() == type) {
1087 return tab.index();
1088 }
1089 }
1090 Unexpected("Index in TabbedSelector::indexByType.");
1091 }
1092
getTab(int index)1093 not_null<TabbedSelector::Tab*> TabbedSelector::getTab(int index) {
1094 return &(_tabs[index]);
1095 }
1096
getTab(int index) const1097 not_null<const TabbedSelector::Tab*> TabbedSelector::getTab(int index) const {
1098 return &_tabs[index];
1099 }
1100
currentTab()1101 not_null<TabbedSelector::Tab*> TabbedSelector::currentTab() {
1102 return &_tabs[indexByType(_currentTabType)];
1103 }
1104
currentTab() const1105 not_null<const TabbedSelector::Tab*> TabbedSelector::currentTab() const {
1106 return &_tabs[indexByType(_currentTabType)];
1107 }
1108
Inner(QWidget * parent,not_null<Window::SessionController * > controller)1109 TabbedSelector::Inner::Inner(
1110 QWidget *parent,
1111 not_null<Window::SessionController*> controller)
1112 : RpWidget(parent)
1113 , _controller(controller) {
1114 }
1115
scrollToRequests() const1116 rpl::producer<int> TabbedSelector::Inner::scrollToRequests() const {
1117 return _scrollToRequests.events();
1118 }
1119
disableScrollRequests() const1120 rpl::producer<bool> TabbedSelector::Inner::disableScrollRequests() const {
1121 return _disableScrollRequests.events();
1122 }
1123
scrollTo(int y)1124 void TabbedSelector::Inner::scrollTo(int y) {
1125 _scrollToRequests.fire_copy(y);
1126 }
1127
disableScroll(bool disabled)1128 void TabbedSelector::Inner::disableScroll(bool disabled) {
1129 _disableScrollRequests.fire_copy(disabled);
1130 }
1131
visibleTopBottomUpdated(int visibleTop,int visibleBottom)1132 void TabbedSelector::Inner::visibleTopBottomUpdated(
1133 int visibleTop,
1134 int visibleBottom) {
1135 _visibleTop = visibleTop;
1136 _visibleBottom = visibleBottom;
1137 }
1138
setMinimalHeight(int newWidth,int newMinimalHeight)1139 void TabbedSelector::Inner::setMinimalHeight(
1140 int newWidth,
1141 int newMinimalHeight) {
1142 if (_minimalHeight != newMinimalHeight) {
1143 _minimalHeight = newMinimalHeight;
1144 resizeToWidth(newWidth);
1145 } else if (newWidth != width()) {
1146 resizeToWidth(newWidth);
1147 }
1148 }
1149
resizeGetHeight(int newWidth)1150 int TabbedSelector::Inner::resizeGetHeight(int newWidth) {
1151 auto result = std::max(
1152 countDesiredHeight(newWidth),
1153 minimalHeight());
1154 if (result != height()) {
1155 update();
1156 }
1157 return result;
1158 }
1159
minimalHeight() const1160 int TabbedSelector::Inner::minimalHeight() const {
1161 return (_minimalHeight > 0)
1162 ? _minimalHeight
1163 : (st::emojiPanMaxHeight - st::emojiFooterHeight);
1164 }
1165
hideFinished()1166 void TabbedSelector::Inner::hideFinished() {
1167 processHideFinished();
1168 if (auto footer = getFooter()) {
1169 footer->processHideFinished();
1170 }
1171 }
1172
panelHideFinished()1173 void TabbedSelector::Inner::panelHideFinished() {
1174 hideFinished();
1175 processPanelHideFinished();
1176 if (auto footer = getFooter()) {
1177 footer->processPanelHideFinished();
1178 }
1179 }
1180
InnerFooter(QWidget * parent)1181 TabbedSelector::InnerFooter::InnerFooter(QWidget *parent)
1182 : RpWidget(parent) {
1183 resize(st::emojiPanWidth, st::emojiFooterHeight);
1184 }
1185
1186 } // namespace ChatHelpers
1187