tabbed_search.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "ui/controls/tabbed_search.h"
  8. #include "base/qt_signal_producer.h"
  9. #include "lang/lang_keys.h"
  10. #include "ui/widgets/fields/input_field.h"
  11. #include "ui/wrap/fade_wrap.h"
  12. #include "ui/widgets/buttons.h"
  13. #include "ui/painter.h"
  14. #include "ui/rect.h"
  15. #include "ui/ui_utility.h"
  16. #include "styles/style_chat_helpers.h"
  17. #include <QtWidgets/QApplication>
  18. namespace Ui {
  19. namespace {
  20. constexpr auto kDebounceTimeout = crl::time(400);
  21. constexpr auto kCategoryIconSizeOverride = 22;
  22. class GroupsStrip final : public RpWidget {
  23. public:
  24. GroupsStrip(
  25. QWidget *parent,
  26. const style::TabbedSearch &st,
  27. rpl::producer<std::vector<EmojiGroup>> groups,
  28. Text::CustomEmojiFactory factory);
  29. void scrollByWheel(QWheelEvent *e);
  30. struct Chosen {
  31. not_null<const EmojiGroup*> group;
  32. int iconLeft = 0;
  33. int iconRight = 0;
  34. };
  35. [[nodiscard]] rpl::producer<Chosen> chosen() const;
  36. void clearChosen();
  37. [[nodiscard]] rpl::producer<int> moveRequests() const;
  38. private:
  39. struct Button {
  40. EmojiGroup group;
  41. QString iconId;
  42. std::unique_ptr<Text::CustomEmoji> icon;
  43. };
  44. void init(rpl::producer<std::vector<EmojiGroup>> groups);
  45. void set(std::vector<EmojiGroup> list);
  46. void paintEvent(QPaintEvent *e) override;
  47. void mouseMoveEvent(QMouseEvent *e) override;
  48. void mousePressEvent(QMouseEvent *e) override;
  49. void mouseReleaseEvent(QMouseEvent *e) override;
  50. void fireChosenGroup();
  51. static inline auto FindById(auto &&buttons, QStringView id) {
  52. return ranges::find(buttons, id, &Button::iconId);
  53. }
  54. const style::TabbedSearch &_st;
  55. const Text::CustomEmojiFactory _factory;
  56. std::vector<Button> _buttons;
  57. rpl::event_stream<Chosen> _chosenGroup;
  58. rpl::event_stream<int> _moveRequests;
  59. QPoint _globalPressPoint, _globalLastPoint;
  60. bool _dragging = false;
  61. int _pressed = -1;
  62. int _chosen = -1;
  63. };
  64. [[nodiscard]] std::vector<QString> FieldQuery(not_null<InputField*> field) {
  65. if (const auto last = field->getLastText(); !last.isEmpty()) {
  66. return { last };
  67. }
  68. return {};
  69. }
  70. GroupsStrip::GroupsStrip(
  71. QWidget *parent,
  72. const style::TabbedSearch &st,
  73. rpl::producer<std::vector<EmojiGroup>> groups,
  74. Text::CustomEmojiFactory factory)
  75. : RpWidget(parent)
  76. , _st(st)
  77. , _factory(std::move(factory)) {
  78. init(std::move(groups));
  79. }
  80. rpl::producer<GroupsStrip::Chosen> GroupsStrip::chosen() const {
  81. return _chosenGroup.events();
  82. }
  83. rpl::producer<int> GroupsStrip::moveRequests() const {
  84. return _moveRequests.events();
  85. }
  86. void GroupsStrip::clearChosen() {
  87. if (const auto chosen = std::exchange(_chosen, -1); chosen >= 0) {
  88. update();
  89. }
  90. }
  91. void GroupsStrip::init(rpl::producer<std::vector<EmojiGroup>> groups) {
  92. std::move(
  93. groups
  94. ) | rpl::start_with_next([=](std::vector<EmojiGroup> &&list) {
  95. set(std::move(list));
  96. }, lifetime());
  97. setCursor(style::cur_pointer);
  98. }
  99. void GroupsStrip::set(std::vector<EmojiGroup> list) {
  100. const auto chosen = (_chosen >= 0)
  101. ? _buttons[_chosen].group.iconId
  102. : QString();
  103. auto existing = std::move(_buttons);
  104. const auto updater = [=](const QString &iconId) {
  105. return [=] {
  106. const auto i = FindById(_buttons, iconId);
  107. if (i != end(_buttons)) {
  108. const auto index = i - begin(_buttons);
  109. const auto single = _st.groupWidth;
  110. update(index * single, 0, single, height());
  111. }
  112. };
  113. };
  114. for (auto &group : list) {
  115. const auto i = FindById(existing, group.iconId);
  116. if (i != end(existing)) {
  117. _buttons.push_back(std::move(*i));
  118. existing.erase(i);
  119. } else {
  120. const auto loopCount = 1;
  121. const auto stopAtLastFrame = true;
  122. _buttons.push_back({
  123. .iconId = group.iconId,
  124. .icon = std::make_unique<Text::LimitedLoopsEmoji>(
  125. _factory(
  126. group.iconId,
  127. { .repaint = updater(group.iconId) }),
  128. loopCount,
  129. stopAtLastFrame),
  130. });
  131. }
  132. _buttons.back().group = std::move(group);
  133. }
  134. resize(_buttons.size() * _st.groupWidth, height());
  135. if (!chosen.isEmpty()) {
  136. const auto i = FindById(_buttons, chosen);
  137. if (i != end(_buttons)) {
  138. _chosen = (i - begin(_buttons));
  139. fireChosenGroup();
  140. } else {
  141. _chosen = -1;
  142. }
  143. }
  144. update();
  145. }
  146. void GroupsStrip::paintEvent(QPaintEvent *e) {
  147. auto p = QPainter(this);
  148. auto index = 0;
  149. const auto single = _st.groupWidth;
  150. const auto skip = _st.groupSkip;
  151. const auto height = this->height();
  152. const auto clip = e->rect();
  153. const auto now = crl::now();
  154. for (const auto &button : _buttons) {
  155. const auto left = index * single;
  156. const auto top = 0;
  157. const auto size = SearchWithGroups::IconSizeOverride();
  158. if (_chosen == index) {
  159. auto hq = PainterHighQualityEnabler(p);
  160. p.setPen(Qt::NoPen);
  161. p.setBrush(_st.bgActive);
  162. p.drawEllipse(
  163. left + skip,
  164. top + (height - single) / 2 + skip,
  165. single - 2 * skip,
  166. single - 2 * skip);
  167. }
  168. if (QRect(left, top, single, height).intersects(clip)) {
  169. button.icon->paint(p, {
  170. .textColor = (_chosen == index ? _st.fgActive : _st.fg)->c,
  171. .now = now,
  172. .position = QPoint(left, top) + QPoint(
  173. (single - size) / 2,
  174. (height - size) / 2),
  175. });
  176. }
  177. ++index;
  178. }
  179. }
  180. void GroupsStrip::scrollByWheel(QWheelEvent *e) {
  181. auto horizontal = (e->angleDelta().x() != 0);
  182. auto vertical = (e->angleDelta().y() != 0);
  183. if (!horizontal && !vertical) {
  184. return;
  185. }
  186. const auto delta = horizontal
  187. ? ((style::RightToLeft() ? -1 : 1) * (e->pixelDelta().x()
  188. ? e->pixelDelta().x()
  189. : e->angleDelta().x()))
  190. : (e->pixelDelta().y()
  191. ? e->pixelDelta().y()
  192. : e->angleDelta().y());
  193. _moveRequests.fire_copy(delta);
  194. }
  195. void GroupsStrip::mouseMoveEvent(QMouseEvent *e) {
  196. const auto point = e->globalPos();
  197. if (!_dragging) {
  198. const auto distance = (point - _globalPressPoint).manhattanLength();
  199. if (distance >= QApplication::startDragDistance()) {
  200. _dragging = true;
  201. _globalLastPoint = _globalPressPoint;
  202. }
  203. }
  204. if (_dragging) {
  205. const auto delta = (point - _globalLastPoint).x();
  206. _globalLastPoint = point;
  207. _moveRequests.fire_copy(delta);
  208. }
  209. }
  210. void GroupsStrip::mousePressEvent(QMouseEvent *e) {
  211. const auto index = e->pos().x() / _st.groupWidth;
  212. const auto chosen = (index < 0 || index >= _buttons.size())
  213. ? -1
  214. : index;
  215. _pressed = chosen;
  216. _globalPressPoint = e->globalPos();
  217. }
  218. void GroupsStrip::mouseReleaseEvent(QMouseEvent *e) {
  219. const auto pressed = std::exchange(_pressed, -1);
  220. if (_dragging) {
  221. _dragging = false;
  222. return;
  223. }
  224. const auto index = e->pos().x() / _st.groupWidth;
  225. const auto chosen = (index < 0 || index >= _buttons.size())
  226. ? -1
  227. : index;
  228. if (pressed == chosen && chosen >= 0) {
  229. _chosen = pressed;
  230. fireChosenGroup();
  231. update();
  232. }
  233. }
  234. void GroupsStrip::fireChosenGroup() {
  235. Expects(_chosen >= 0 && _chosen < _buttons.size());
  236. _chosenGroup.fire({
  237. .group = &_buttons[_chosen].group,
  238. .iconLeft = _chosen * _st.groupWidth,
  239. .iconRight = (_chosen + 1) * _st.groupWidth,
  240. });
  241. }
  242. } // namespace
  243. const QString &PremiumGroupFakeEmoticon() {
  244. static const auto result = u"*premium"_q;
  245. return result;
  246. }
  247. SearchWithGroups::SearchWithGroups(
  248. QWidget *parent,
  249. SearchDescriptor descriptor)
  250. : RpWidget(parent)
  251. , _st(descriptor.st)
  252. , _search(CreateChild<FadeWrapScaled<IconButton>>(
  253. this,
  254. object_ptr<IconButton>(this, _st.search)))
  255. , _back(CreateChild<FadeWrapScaled<IconButton>>(
  256. this,
  257. object_ptr<IconButton>(this, _st.back)))
  258. , _cancel(CreateChild<CrossButton>(this, _st.cancel))
  259. , _field(CreateChild<InputField>(this, _st.field, tr::lng_dlg_filter()))
  260. , _groups(CreateChild<FadeWrapScaled<RpWidget>>(
  261. this,
  262. object_ptr<GroupsStrip>(
  263. this,
  264. _st,
  265. std::move(descriptor.groups),
  266. std::move(descriptor.customEmojiFactory))))
  267. , _fade(CreateChild<RpWidget>(this))
  268. , _debounceTimer([=] { _debouncedQuery = _query.current(); }) {
  269. initField();
  270. initGroups();
  271. initButtons();
  272. initEdges();
  273. _inited = true;
  274. }
  275. anim::type SearchWithGroups::animated() const {
  276. return _inited ? anim::type::normal : anim::type::instant;
  277. }
  278. void SearchWithGroups::initField() {
  279. _field->changes(
  280. ) | rpl::start_with_next([=] {
  281. const auto last = FieldQuery(_field);
  282. _query = last;
  283. const auto empty = last.empty();
  284. _fieldEmpty = empty;
  285. if (empty) {
  286. _debounceTimer.cancel();
  287. _debouncedQuery = last;
  288. } else {
  289. _debounceTimer.callOnce(kDebounceTimeout);
  290. _chosenGroup = QString();
  291. scrollGroupsToStart();
  292. }
  293. }, _field->lifetime());
  294. _fieldPlaceholderWidth = tr::lng_dlg_filter(
  295. ) | rpl::map([=](const QString &value) {
  296. return _st.field.placeholderFont->width(value);
  297. }) | rpl::after_next([=] {
  298. resizeToWidth(width());
  299. });
  300. const auto last = FieldQuery(_field);
  301. _query = last;
  302. _debouncedQuery = last;
  303. _fieldEmpty = last.empty();
  304. _fieldEmpty.value(
  305. ) | rpl::start_with_next([=](bool empty) {
  306. _cancel->toggle(!empty, animated());
  307. _groups->toggle(empty, animated());
  308. resizeToWidth(width());
  309. }, lifetime());
  310. }
  311. void SearchWithGroups::initGroups() {
  312. const auto widget = static_cast<GroupsStrip*>(_groups->entity());
  313. const auto &search = _st.search;
  314. _fadeLeftStart = search.iconPosition.x() + search.icon.width();
  315. _groups->move(_fadeLeftStart + _st.defaultFieldWidth, 0);
  316. widget->resize(widget->width(), _st.height);
  317. widget->widthValue(
  318. ) | rpl::filter([=] {
  319. return (width() > 0);
  320. }) | rpl::start_with_next([=] {
  321. resizeToWidth(width());
  322. }, widget->lifetime());
  323. widget->chosen(
  324. ) | rpl::start_with_next([=](const GroupsStrip::Chosen &chosen) {
  325. _chosenGroup = chosen.group->iconId;
  326. _query = (chosen.group->type == EmojiGroupType::Premium)
  327. ? std::vector{ PremiumGroupFakeEmoticon() }
  328. : chosen.group->emoticons;
  329. _debouncedQuery = chosen.group->emoticons;
  330. _debounceTimer.cancel();
  331. scrollGroupsToIcon(chosen.iconLeft, chosen.iconRight);
  332. }, lifetime());
  333. widget->moveRequests(
  334. ) | rpl::start_with_next([=](int delta) {
  335. moveGroupsBy(width(), delta);
  336. }, lifetime());
  337. _chosenGroup.value(
  338. ) | rpl::map([=](const QString &id) {
  339. return id.isEmpty();
  340. }) | rpl::start_with_next([=](bool empty) {
  341. _search->toggle(empty, animated());
  342. _back->toggle(!empty, animated());
  343. if (empty) {
  344. widget->clearChosen();
  345. if (_field->getLastText().isEmpty()) {
  346. _query = {};
  347. _debouncedQuery = {};
  348. _debounceTimer.cancel();
  349. }
  350. } else {
  351. _field->setText({});
  352. }
  353. }, lifetime());
  354. }
  355. void SearchWithGroups::scrollGroupsToIcon(int iconLeft, int iconRight) {
  356. const auto single = _st.groupWidth;
  357. const auto fadeRight = _fadeLeftStart + _st.fadeLeft.width();
  358. if (_groups->x() < fadeRight + single - iconLeft) {
  359. scrollGroupsTo(fadeRight + single - iconLeft);
  360. } else if (_groups->x() > width() - single - iconRight) {
  361. scrollGroupsTo(width() - single - iconRight);
  362. } else {
  363. _groupsLeftAnimation.stop();
  364. }
  365. }
  366. void SearchWithGroups::scrollGroupsToStart() {
  367. scrollGroupsTo(width());
  368. }
  369. void SearchWithGroups::scrollGroupsTo(int left) {
  370. left = clampGroupsLeft(width(), left);
  371. _groupsLeftTo = left;
  372. const auto delta = _groupsLeftTo - _groups->x();
  373. if (!delta) {
  374. _groupsLeftAnimation.stop();
  375. return;
  376. }
  377. _groupsLeftAnimation.start([=] {
  378. const auto d = int(base::SafeRound(_groupsLeftAnimation.value(0)));
  379. moveGroupsTo(width(), _groupsLeftTo - d);
  380. }, delta, 0, st::slideWrapDuration, anim::sineInOut);
  381. }
  382. void SearchWithGroups::initEdges() {
  383. paintRequest() | rpl::start_with_next([=](QRect clip) {
  384. QPainter(this).fillRect(clip, _st.bg);
  385. }, lifetime());
  386. const auto makeEdge = [&](bool left) {
  387. const auto edge = CreateChild<RpWidget>(this);
  388. const auto size = QSize(height() / 2, height());
  389. edge->setAttribute(Qt::WA_TransparentForMouseEvents);
  390. edge->resize(size);
  391. if (left) {
  392. edge->move(0, 0);
  393. } else {
  394. widthValue(
  395. ) | rpl::start_with_next([=](int width) {
  396. edge->move(width - edge->width(), 0);
  397. }, edge->lifetime());
  398. }
  399. edge->paintRequest(
  400. ) | rpl::start_with_next([=] {
  401. const auto ratio = edge->devicePixelRatioF();
  402. ensureRounding(height(), ratio);
  403. const auto size = _rounding.height();
  404. const auto half = size / 2;
  405. QPainter(edge).drawImage(
  406. QPoint(),
  407. _rounding,
  408. QRect(left ? 0 : _rounding.width() - half, 0, half, size));
  409. }, edge->lifetime());
  410. };
  411. makeEdge(true);
  412. makeEdge(false);
  413. _fadeOpacity.changes(
  414. ) | rpl::start_with_next([=] {
  415. _fade->update();
  416. }, _fade->lifetime());
  417. _fade->paintRequest(
  418. ) | rpl::start_with_next([=](QRect clip) {
  419. auto p = QPainter(_fade);
  420. p.setOpacity(_fadeOpacity.current());
  421. const auto fill = QRect(0, 0, _fadeLeftStart, _st.height);
  422. if (fill.intersects(clip)) {
  423. p.fillRect(fill, _st.bg);
  424. }
  425. const auto icon = QRect(
  426. _fadeLeftStart,
  427. 0,
  428. _st.fadeLeft.width(),
  429. _st.height);
  430. if (clip.intersects(icon)) {
  431. _st.fadeLeft.fill(p, icon);
  432. }
  433. }, _fade->lifetime());
  434. _fade->setAttribute(Qt::WA_TransparentForMouseEvents);
  435. style::PaletteChanged(
  436. ) | rpl::start_with_next([=] {
  437. _rounding = QImage();
  438. }, lifetime());
  439. }
  440. void SearchWithGroups::initButtons() {
  441. _cancel->setClickedCallback([=] {
  442. _field->setText(QString());
  443. });
  444. _back->entity()->setClickedCallback([=] {
  445. _chosenGroup = QString();
  446. scrollGroupsToStart();
  447. });
  448. _search->entity()->setClickedCallback([=] {
  449. _field->setFocus();
  450. scrollGroupsToStart();
  451. });
  452. _field->focusedChanges(
  453. ) | rpl::filter(rpl::mappers::_1) | rpl::start_with_next([=] {
  454. scrollGroupsToStart();
  455. }, _field->lifetime());
  456. _field->raise();
  457. _fade->raise();
  458. _search->raise();
  459. _back->raise();
  460. _cancel->raise();
  461. }
  462. void SearchWithGroups::ensureRounding(int size, float64 ratio) {
  463. const auto rounded = qRound(size * ratio);
  464. const auto full = QSize(rounded + 4, rounded);
  465. if (_rounding.size() != full) {
  466. _rounding = QImage(full, QImage::Format_ARGB32_Premultiplied);
  467. _rounding.fill(_st.outer->c);
  468. auto p = QPainter(&_rounding);
  469. auto hq = PainterHighQualityEnabler(p);
  470. p.setCompositionMode(QPainter::CompositionMode_Source);
  471. p.setBrush(Qt::transparent);
  472. p.setPen(Qt::NoPen);
  473. p.drawRoundedRect(QRect(QPoint(), full), rounded / 2., rounded / 2.);
  474. }
  475. _rounding.setDevicePixelRatio(ratio);
  476. }
  477. rpl::producer<> SearchWithGroups::escapes() const {
  478. return _field->cancelled();
  479. }
  480. rpl::producer<std::vector<QString>> SearchWithGroups::queryValue() const {
  481. return _query.value();
  482. }
  483. auto SearchWithGroups::debouncedQueryValue() const
  484. -> rpl::producer<std::vector<QString>> {
  485. return _debouncedQuery.value();
  486. }
  487. void SearchWithGroups::cancel() {
  488. _field->setText(QString());
  489. _chosenGroup = QString();
  490. scrollGroupsToStart();
  491. }
  492. void SearchWithGroups::setLoading(bool loading) {
  493. _cancel->setLoadingAnimation(loading);
  494. }
  495. void SearchWithGroups::stealFocus() {
  496. if (!_focusTakenFrom) {
  497. _focusTakenFrom = QApplication::focusWidget();
  498. }
  499. _field->setFocus();
  500. }
  501. void SearchWithGroups::returnFocus() {
  502. if (_field && _focusTakenFrom) {
  503. if (_field->hasFocus()) {
  504. _focusTakenFrom->setFocus();
  505. }
  506. _focusTakenFrom = nullptr;
  507. }
  508. }
  509. int SearchWithGroups::IconSizeOverride() {
  510. return style::ConvertScale(kCategoryIconSizeOverride);
  511. }
  512. int SearchWithGroups::resizeGetHeight(int newWidth) {
  513. if (!newWidth) {
  514. return _st.height;
  515. }
  516. _back->moveToLeft(0, 0, newWidth);
  517. _search->moveToLeft(0, 0, newWidth);
  518. _cancel->moveToRight(0, 0, newWidth);
  519. moveGroupsBy(newWidth, 0);
  520. const auto fadeWidth = _fadeLeftStart + _st.fadeLeft.width();
  521. const auto fade = QRect(0, 0, fadeWidth, _st.height);
  522. _fade->setGeometry(fade);
  523. return _st.height;
  524. }
  525. void SearchWithGroups::wheelEvent(QWheelEvent *e) {
  526. static_cast<GroupsStrip*>(_groups->entity())->scrollByWheel(e);
  527. }
  528. int SearchWithGroups::clampGroupsLeft(int width, int desiredLeft) const {
  529. const auto groupsLeftDefault = _fadeLeftStart + _st.defaultFieldWidth;
  530. const auto groupsLeftMin = width - _groups->entity()->width();
  531. const auto groupsLeftMax = std::max(groupsLeftDefault, groupsLeftMin);
  532. return std::clamp(desiredLeft, groupsLeftMin, groupsLeftMax);
  533. }
  534. void SearchWithGroups::moveGroupsBy(int width, int delta) {
  535. moveGroupsTo(width, _groups->x() + delta);
  536. }
  537. void SearchWithGroups::moveGroupsTo(int width, int to) {
  538. const auto groupsLeft = clampGroupsLeft(width, to);
  539. _groups->move(groupsLeft, 0);
  540. const auto placeholderMargins = _st.field.textMargins
  541. + _st.field.placeholderMargins;
  542. const auto placeholderWidth = _fieldPlaceholderWidth.current();
  543. const auto fieldWidthMin = std::min(
  544. rect::m::sum::h(placeholderMargins) + placeholderWidth,
  545. _st.defaultFieldWidth);
  546. const auto fieldWidth = _fieldEmpty.current()
  547. ? std::max(groupsLeft - _st.search.width, fieldWidthMin)
  548. : (width - _fadeLeftStart - _st.cancel.width);
  549. _field->resizeToWidth(fieldWidth);
  550. const auto fieldLeft = _fieldEmpty.current()
  551. ? (groupsLeft - fieldWidth)
  552. : _fadeLeftStart;
  553. _field->moveToLeft(fieldLeft, 0);
  554. if (fieldLeft >= _fadeLeftStart) {
  555. if (!_fade->isHidden()) {
  556. _fade->hide();
  557. }
  558. } else {
  559. if (_fade->isHidden()) {
  560. _fade->show();
  561. }
  562. _fadeOpacity = (fieldLeft < _fadeLeftStart / 2)
  563. ? 1.
  564. : (_fadeLeftStart - fieldLeft) / float64(_fadeLeftStart / 2);
  565. }
  566. }
  567. TabbedSearch::TabbedSearch(
  568. not_null<RpWidget*> parent,
  569. const style::EmojiPan &st,
  570. SearchDescriptor &&descriptor)
  571. : _st(st)
  572. , _search(parent, std::move(descriptor)) {
  573. _search.move(_st.searchMargin.left(), _st.searchMargin.top());
  574. parent->widthValue(
  575. ) | rpl::start_with_next([=](int width) {
  576. _search.resizeToWidth(width - rect::m::sum::h(_st.searchMargin));
  577. }, _search.lifetime());
  578. }
  579. int TabbedSearch::height() const {
  580. return _search.height() + rect::m::sum::v(_st.searchMargin);
  581. }
  582. QImage TabbedSearch::grab() {
  583. return Ui::GrabWidgetToImage(&_search);
  584. }
  585. void TabbedSearch::cancel() {
  586. _search.cancel();
  587. }
  588. void TabbedSearch::setLoading(bool loading) {
  589. _search.setLoading(loading);
  590. }
  591. void TabbedSearch::stealFocus() {
  592. _search.stealFocus();
  593. }
  594. void TabbedSearch::returnFocus() {
  595. _search.returnFocus();
  596. }
  597. rpl::producer<> TabbedSearch::escapes() const {
  598. return _search.escapes();
  599. }
  600. rpl::producer<std::vector<QString>> TabbedSearch::queryValue() const {
  601. return _search.queryValue();
  602. }
  603. auto TabbedSearch::debouncedQueryValue() const
  604. -> rpl::producer<std::vector<QString>> {
  605. return _search.debouncedQueryValue();
  606. }
  607. } // namespace Ui