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