multi_select.cpp 27 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100
  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/widgets/multi_select.h"
  8. #include "ui/widgets/buttons.h"
  9. #include "ui/widgets/fields/input_field.h"
  10. #include "ui/widgets/scroll_area.h"
  11. #include "ui/effects/animations.h"
  12. #include "ui/effects/cross_animation.h"
  13. #include "ui/text/text_options.h"
  14. #include "ui/painter.h"
  15. #include "ui/ui_utility.h"
  16. #include "lang/lang_keys.h"
  17. #include <set>
  18. namespace Ui {
  19. namespace {
  20. constexpr int kWideScale = 3;
  21. class Item {
  22. public:
  23. Item(
  24. const style::MultiSelectItem &st,
  25. uint64 id,
  26. const QString &text,
  27. style::color color,
  28. MultiSelect::PaintRoundImage &&paintRoundImage);
  29. uint64 id() const {
  30. return _id;
  31. }
  32. int getWidth() const {
  33. return _width;
  34. }
  35. QRect rect() const {
  36. return QRect(_x, _y, _width, _st.height);
  37. }
  38. bool isOverDelete() const {
  39. return _overDelete;
  40. }
  41. void setActive(bool active) {
  42. _active = active;
  43. }
  44. void setPosition(int x, int y, int outerWidth, int maxVisiblePadding);
  45. QRect paintArea(int outerWidth) const;
  46. void setUpdateCallback(Fn<void()> updateCallback) {
  47. _updateCallback = updateCallback;
  48. }
  49. void setText(const QString &text);
  50. void paint(Painter &p, int outerWidth);
  51. void mouseMoveEvent(QPoint point);
  52. void leaveEvent();
  53. void showAnimated() {
  54. setVisibleAnimated(true);
  55. }
  56. void hideAnimated() {
  57. setVisibleAnimated(false);
  58. }
  59. bool hideFinished() const {
  60. return (_hiding && !_visibility.animating());
  61. }
  62. private:
  63. void setOver(bool over);
  64. void paintOnce(Painter &p, int x, int y, int outerWidth);
  65. void paintDeleteButton(
  66. Painter &p,
  67. int x,
  68. int y,
  69. int outerWidth,
  70. float64 overOpacity);
  71. bool paintCached(Painter &p, int x, int y, int outerWidth);
  72. void prepareCache();
  73. void setVisibleAnimated(bool visible);
  74. const style::MultiSelectItem &_st;
  75. uint64 _id;
  76. struct SlideAnimation {
  77. SlideAnimation(
  78. Fn<void()> updateCallback,
  79. int fromX,
  80. int toX,
  81. int y,
  82. float64 duration)
  83. : fromX(fromX)
  84. , toX(toX)
  85. , y(y) {
  86. x.start(updateCallback, fromX, toX, duration);
  87. }
  88. Ui::Animations::Simple x;
  89. int fromX, toX;
  90. int y;
  91. };
  92. std::vector<SlideAnimation> _copies;
  93. int _x = -1;
  94. int _y = -1;
  95. int _width = 0;
  96. Text::String _text;
  97. style::color _color;
  98. bool _over = false;
  99. QPixmap _cache;
  100. Ui::Animations::Simple _visibility;
  101. Ui::Animations::Simple _overOpacity;
  102. bool _overDelete = false;
  103. bool _active = false;
  104. MultiSelect::PaintRoundImage _paintRoundImage;
  105. Fn<void()> _updateCallback;
  106. bool _hiding = false;
  107. };
  108. Item::Item(
  109. const style::MultiSelectItem &st,
  110. uint64 id,
  111. const QString &text,
  112. style::color color,
  113. MultiSelect::PaintRoundImage &&paintRoundImage)
  114. : _st(st)
  115. , _id(id)
  116. , _color(color)
  117. , _paintRoundImage(std::move(paintRoundImage)) {
  118. setText(text);
  119. }
  120. void Item::setText(const QString &text) {
  121. _text.setText(_st.style, text, NameTextOptions());
  122. _width = _st.height
  123. + _st.padding.left()
  124. + _text.maxWidth()
  125. + _st.padding.right();
  126. accumulate_min(_width, _st.maxWidth);
  127. }
  128. void Item::paint(Painter &p, int outerWidth) {
  129. if (!_cache.isNull() && !_visibility.animating()) {
  130. if (_hiding) {
  131. return;
  132. } else {
  133. _cache = QPixmap();
  134. }
  135. }
  136. if (_copies.empty()) {
  137. paintOnce(p, _x, _y, outerWidth);
  138. } else {
  139. for (auto i = _copies.begin(), e = _copies.end(); i != e;) {
  140. auto x = qRound(i->x.value(_x));
  141. auto y = i->y;
  142. auto animating = i->x.animating();
  143. if (animating || (y == _y)) {
  144. paintOnce(p, x, y, outerWidth);
  145. }
  146. if (animating) {
  147. ++i;
  148. } else {
  149. i = _copies.erase(i);
  150. e = _copies.end();
  151. }
  152. }
  153. }
  154. }
  155. void Item::paintOnce(Painter &p, int x, int y, int outerWidth) {
  156. if (!_cache.isNull()) {
  157. paintCached(p, x, y, outerWidth);
  158. return;
  159. }
  160. auto radius = _st.height / 2;
  161. auto inner = style::rtlrect(
  162. x + radius,
  163. y,
  164. _width - radius,
  165. _st.height,
  166. outerWidth);
  167. auto clipEnabled = p.hasClipping();
  168. auto clip = clipEnabled ? p.clipRegion() : QRegion();
  169. p.setClipRect(inner);
  170. p.setPen(Qt::NoPen);
  171. p.setBrush(_active ? _st.textActiveBg : _st.textBg);
  172. {
  173. PainterHighQualityEnabler hq(p);
  174. p.drawRoundedRect(
  175. style::rtlrect(x, y, _width, _st.height, outerWidth),
  176. radius,
  177. radius);
  178. }
  179. if (clipEnabled) {
  180. p.setClipRegion(clip);
  181. } else {
  182. p.setClipping(false);
  183. }
  184. auto overOpacity = _overOpacity.value(_over ? 1. : 0.);
  185. if (overOpacity < 1.) {
  186. _paintRoundImage(p, x, y, outerWidth, _st.height);
  187. }
  188. if (overOpacity > 0.) {
  189. paintDeleteButton(p, x, y, outerWidth, overOpacity);
  190. }
  191. auto textLeft = _st.height + _st.padding.left();
  192. auto textWidth = _width - textLeft - _st.padding.right();
  193. p.setPen(_active ? _st.textActiveFg : _st.textFg);
  194. _text.drawLeftElided(
  195. p,
  196. x + textLeft,
  197. y + _st.padding.top(),
  198. textWidth,
  199. outerWidth);
  200. }
  201. void Item::paintDeleteButton(
  202. Painter &p,
  203. int x,
  204. int y,
  205. int outerWidth,
  206. float64 overOpacity) {
  207. p.setOpacity(overOpacity);
  208. p.setPen(Qt::NoPen);
  209. p.setBrush(_color);
  210. {
  211. PainterHighQualityEnabler hq(p);
  212. p.drawEllipse(
  213. style::rtlrect(x, y, _st.height, _st.height, outerWidth));
  214. }
  215. CrossAnimation::paint(
  216. p,
  217. _st.deleteCross,
  218. _st.deleteFg,
  219. x,
  220. y,
  221. outerWidth,
  222. overOpacity);
  223. p.setOpacity(1.);
  224. }
  225. bool Item::paintCached(Painter &p, int x, int y, int outerWidth) {
  226. PainterHighQualityEnabler hq(p);
  227. auto opacity = _visibility.value(_hiding ? 0. : 1.);
  228. auto height = opacity * _cache.height() / _cache.devicePixelRatio();
  229. auto width = opacity * _cache.width() / _cache.devicePixelRatio();
  230. p.setOpacity(opacity);
  231. p.drawPixmap(
  232. style::rtlrect(
  233. x + (_width - width) / 2.,
  234. y + (_st.height - height) / 2.,
  235. width,
  236. height,
  237. outerWidth),
  238. _cache);
  239. p.setOpacity(1.);
  240. return true;
  241. }
  242. void Item::mouseMoveEvent(QPoint point) {
  243. if (!_cache.isNull()) {
  244. return;
  245. }
  246. _overDelete = QRect(0, 0, _st.height, _st.height).contains(point);
  247. setOver(true);
  248. }
  249. void Item::leaveEvent() {
  250. _overDelete = false;
  251. setOver(false);
  252. }
  253. void Item::setPosition(int x, int y, int outerWidth, int maxVisiblePadding) {
  254. if (_x >= 0 && _y >= 0 && (_x != x || _y != y)) {
  255. // Make an animation if it is not the first setPosition().
  256. auto found = false;
  257. auto leftHidden = -_width - maxVisiblePadding;
  258. auto rightHidden = outerWidth + maxVisiblePadding;
  259. for (auto i = _copies.begin(), e = _copies.end(); i != e;) {
  260. if (i->x.animating()) {
  261. if (i->y == y) {
  262. i->x.start(_updateCallback, i->toX, x, _st.duration);
  263. found = true;
  264. } else {
  265. i->x.start(
  266. _updateCallback,
  267. i->fromX,
  268. (i->toX > i->fromX) ? rightHidden : leftHidden,
  269. _st.duration);
  270. }
  271. ++i;
  272. } else {
  273. i = _copies.erase(i);
  274. e = _copies.end();
  275. }
  276. }
  277. if (_copies.empty()) {
  278. if (_y == y) {
  279. auto copy = SlideAnimation(
  280. _updateCallback,
  281. _x,
  282. x,
  283. _y,
  284. _st.duration);
  285. _copies.push_back(std::move(copy));
  286. } else {
  287. auto copyHiding = SlideAnimation(
  288. _updateCallback,
  289. _x,
  290. (y > _y) ? rightHidden : leftHidden,
  291. _y,
  292. _st.duration);
  293. _copies.push_back(std::move(copyHiding));
  294. auto copyShowing = SlideAnimation(
  295. _updateCallback,
  296. (y > _y) ? leftHidden : rightHidden,
  297. x,
  298. y,
  299. _st.duration);
  300. _copies.push_back(std::move(copyShowing));
  301. }
  302. } else if (!found) {
  303. auto copy = SlideAnimation(
  304. _updateCallback,
  305. (y > _y) ? leftHidden : rightHidden,
  306. x,
  307. y,
  308. _st.duration);
  309. _copies.push_back(std::move(copy));
  310. }
  311. }
  312. _x = x;
  313. _y = y;
  314. }
  315. QRect Item::paintArea(int outerWidth) const {
  316. if (_copies.empty()) {
  317. return rect();
  318. }
  319. auto yMin = 0, yMax = 0;
  320. for (const auto &copy : _copies) {
  321. accumulate_max(yMax, copy.y);
  322. if (yMin) {
  323. accumulate_min(yMin, copy.y);
  324. } else {
  325. yMin = copy.y;
  326. }
  327. }
  328. return QRect(0, yMin, outerWidth, yMax - yMin + _st.height);
  329. }
  330. void Item::prepareCache() {
  331. if (!_cache.isNull()) return;
  332. Assert(!_visibility.animating());
  333. auto cacheWidth = _width * kWideScale * style::DevicePixelRatio();
  334. auto cacheHeight = _st.height * kWideScale * style::DevicePixelRatio();
  335. auto data = QImage(
  336. cacheWidth,
  337. cacheHeight,
  338. QImage::Format_ARGB32_Premultiplied);
  339. data.fill(Qt::transparent);
  340. data.setDevicePixelRatio(style::DevicePixelRatio());
  341. {
  342. Painter p(&data);
  343. paintOnce(
  344. p,
  345. _width * (kWideScale - 1) / 2,
  346. _st.height * (kWideScale - 1) / 2,
  347. cacheWidth);
  348. }
  349. _cache = Ui::PixmapFromImage(std::move(data));
  350. }
  351. void Item::setVisibleAnimated(bool visible) {
  352. _hiding = !visible;
  353. prepareCache();
  354. auto from = visible ? 0. : 1.;
  355. auto to = visible ? 1. : 0.;
  356. auto transition = visible ? anim::bumpy(1.0625) : anim::linear;
  357. _visibility.start(_updateCallback, from, to, _st.duration, transition);
  358. }
  359. void Item::setOver(bool over) {
  360. if (over != _over) {
  361. _over = over;
  362. _overOpacity.start(
  363. _updateCallback,
  364. _over ? 0. : 1.,
  365. _over ? 1. : 0.,
  366. _st.duration);
  367. }
  368. }
  369. } // namespace
  370. class MultiSelect::Inner : public TWidget {
  371. public:
  372. using ScrollCallback = Fn<void(int activeTop, int activeBottom)>;
  373. Inner(
  374. QWidget *parent,
  375. const style::MultiSelect &st,
  376. rpl::producer<QString> placeholder,
  377. const QString &query,
  378. ScrollCallback callback);
  379. [[nodiscard]] QString getQuery() const;
  380. void setQuery(const QString &query);
  381. bool setInnerFocus();
  382. void clearQuery();
  383. void setQueryChangedCallback(Fn<void(const QString &query)> callback);
  384. void setSubmittedCallback(Fn<void(Qt::KeyboardModifiers)> callback);
  385. void setCancelledCallback(Fn<void()> callback);
  386. void addItemInBunch(std::unique_ptr<Item> item);
  387. void finishItemsBunch(AddItemWay way);
  388. void setItemText(uint64 itemId, const QString &text);
  389. void setItemRemovedCallback(Fn<void(uint64 itemId)> callback);
  390. void removeItem(uint64 itemId);
  391. int getItemsCount() const;
  392. QVector<uint64> getItems() const;
  393. bool hasItem(uint64 itemId) const;
  394. void setResizedCallback(Fn<void(int heightDelta)> callback);
  395. ~Inner();
  396. protected:
  397. int resizeGetHeight(int newWidth) override;
  398. void paintEvent(QPaintEvent *e) override;
  399. void leaveEventHook(QEvent *e) override;
  400. void mouseMoveEvent(QMouseEvent *e) override;
  401. void mousePressEvent(QMouseEvent *e) override;
  402. void keyPressEvent(QKeyEvent *e) override;
  403. private:
  404. void cancelled();
  405. void queryChanged();
  406. void fieldFocused();
  407. void computeItemsGeometry(int newWidth);
  408. void updateItemsGeometry();
  409. void updateFieldGeometry();
  410. void updateHasAnyItems(bool hasAnyItems);
  411. void updateSelection(QPoint mousePosition);
  412. void clearSelection() {
  413. updateSelection(QPoint(-1, -1));
  414. }
  415. void updateCursor();
  416. void updateHeightStep();
  417. void finishHeightAnimation();
  418. enum class ChangeActiveWay {
  419. Default,
  420. SkipSetFocus,
  421. };
  422. void setActiveItem(
  423. int active,
  424. ChangeActiveWay skipSetFocus = ChangeActiveWay::Default);
  425. void setActiveItemPrevious();
  426. void setActiveItemNext();
  427. QMargins itemPaintMargins() const;
  428. const style::MultiSelect &_st;
  429. Ui::Animations::Simple _iconOpacity;
  430. ScrollCallback _scrollCallback;
  431. std::set<uint64> _idsMap;
  432. std::vector<std::unique_ptr<Item>> _items;
  433. std::set<std::unique_ptr<Item>> _removingItems;
  434. int _selected = -1;
  435. int _active = -1;
  436. bool _overDelete = false;
  437. int _fieldLeft = 0;
  438. int _fieldTop = 0;
  439. int _fieldWidth = 0;
  440. object_ptr<Ui::InputField> _field;
  441. object_ptr<Ui::CrossButton> _cancel;
  442. int _newHeight = 0;
  443. Ui::Animations::Simple _height;
  444. Fn<void(const QString &query)> _queryChangedCallback;
  445. Fn<void(Qt::KeyboardModifiers)> _submittedCallback;
  446. Fn<void()> _cancelledCallback;
  447. Fn<void(uint64 itemId)> _itemRemovedCallback;
  448. Fn<void(int heightDelta)> _resizedCallback;
  449. };
  450. MultiSelect::MultiSelect(
  451. QWidget *parent,
  452. const style::MultiSelect &st,
  453. rpl::producer<QString> placeholder,
  454. const QString &query)
  455. : RpWidget(parent)
  456. , _st(st)
  457. , _scroll(this, _st.scroll) {
  458. const auto scrollCallback = [=](int activeTop, int activeBottom) {
  459. scrollTo(activeTop, activeBottom);
  460. };
  461. _inner = _scroll->setOwnedWidget(object_ptr<Inner>(
  462. this,
  463. st,
  464. std::move(placeholder),
  465. query,
  466. scrollCallback));
  467. _scroll->installEventFilter(this);
  468. _inner->setResizedCallback([this](int innerHeightDelta) {
  469. auto newHeight = resizeGetHeight(width());
  470. if (innerHeightDelta > 0) {
  471. _scroll->scrollToY(_scroll->scrollTop() + innerHeightDelta);
  472. }
  473. if (newHeight != height()) {
  474. resize(width(), newHeight);
  475. if (_resizedCallback) {
  476. _resizedCallback();
  477. }
  478. }
  479. });
  480. _inner->setQueryChangedCallback([this](const QString &query) {
  481. _scroll->scrollToY(_scroll->scrollTopMax());
  482. if (_queryChangedCallback) {
  483. _queryChangedCallback(query);
  484. }
  485. });
  486. setAttribute(Qt::WA_OpaquePaintEvent);
  487. auto defaultWidth = _st.item.maxWidth + _st.fieldMinWidth + _st.fieldCancelSkip;
  488. resizeToWidth(_st.padding.left() + defaultWidth + _st.padding.right());
  489. }
  490. bool MultiSelect::eventFilter(QObject *o, QEvent *e) {
  491. if (o == _scroll && e->type() == QEvent::KeyPress) {
  492. e->ignore();
  493. return true;
  494. }
  495. return false;
  496. }
  497. void MultiSelect::scrollTo(int activeTop, int activeBottom) {
  498. auto scrollTop = _scroll->scrollTop();
  499. auto scrollHeight = _scroll->height();
  500. auto scrollBottom = scrollTop + scrollHeight;
  501. if (scrollTop > activeTop) {
  502. _scroll->scrollToY(activeTop);
  503. } else if (scrollBottom < activeBottom) {
  504. _scroll->scrollToY(activeBottom - scrollHeight);
  505. }
  506. }
  507. void MultiSelect::setQueryChangedCallback(Fn<void(const QString &query)> callback) {
  508. _queryChangedCallback = std::move(callback);
  509. }
  510. void MultiSelect::setSubmittedCallback(Fn<void(Qt::KeyboardModifiers)> callback) {
  511. _inner->setSubmittedCallback(std::move(callback));
  512. }
  513. void MultiSelect::setCancelledCallback(Fn<void()> callback) {
  514. _inner->setCancelledCallback(std::move(callback));
  515. }
  516. void MultiSelect::setResizedCallback(Fn<void()> callback) {
  517. _resizedCallback = std::move(callback);
  518. }
  519. void MultiSelect::setInnerFocus() {
  520. if (_inner->setInnerFocus()) {
  521. _scroll->scrollToY(_scroll->scrollTopMax());
  522. }
  523. }
  524. void MultiSelect::clearQuery() {
  525. _inner->clearQuery();
  526. }
  527. QString MultiSelect::getQuery() const {
  528. return _inner->getQuery();
  529. }
  530. void MultiSelect::setQuery(const QString &query) {
  531. _inner->setQuery(query);
  532. }
  533. void MultiSelect::addItem(uint64 itemId, const QString &text, style::color color, PaintRoundImage paintRoundImage, AddItemWay way) {
  534. addItemInBunch(itemId, text, color, std::move(paintRoundImage));
  535. _inner->finishItemsBunch(way);
  536. }
  537. void MultiSelect::addItemInBunch(uint64 itemId, const QString &text, style::color color, PaintRoundImage paintRoundImage) {
  538. _inner->addItemInBunch(std::make_unique<Item>(_st.item, itemId, text, color, std::move(paintRoundImage)));
  539. }
  540. void MultiSelect::finishItemsBunch() {
  541. _inner->finishItemsBunch(AddItemWay::SkipAnimation);
  542. }
  543. void MultiSelect::setItemRemovedCallback(Fn<void(uint64 itemId)> callback) {
  544. _inner->setItemRemovedCallback(std::move(callback));
  545. }
  546. void MultiSelect::removeItem(uint64 itemId) {
  547. _inner->removeItem(itemId);
  548. }
  549. int MultiSelect::getItemsCount() const {
  550. return _inner->getItemsCount();
  551. }
  552. QVector<uint64> MultiSelect::getItems() const {
  553. return _inner->getItems();
  554. }
  555. bool MultiSelect::hasItem(uint64 itemId) const {
  556. return _inner->hasItem(itemId);
  557. }
  558. int MultiSelect::resizeGetHeight(int newWidth) {
  559. if (newWidth != _inner->width()) {
  560. _inner->resizeToWidth(newWidth);
  561. }
  562. auto newHeight = qMin(_inner->height(), _st.maxHeight);
  563. _scroll->setGeometryToLeft(0, 0, newWidth, newHeight);
  564. return newHeight;
  565. }
  566. MultiSelect::Inner::Inner(
  567. QWidget *parent,
  568. const style::MultiSelect &st,
  569. rpl::producer<QString> placeholder,
  570. const QString &query,
  571. ScrollCallback callback)
  572. : TWidget(parent)
  573. , _st(st)
  574. , _scrollCallback(std::move(callback))
  575. , _field(this, _st.field, std::move(placeholder), query)
  576. , _cancel(this, _st.fieldCancel) {
  577. _field->customUpDown(true);
  578. _field->focusedChanges(
  579. ) | rpl::filter(rpl::mappers::_1) | rpl::start_with_next([=] {
  580. fieldFocused();
  581. }, _field->lifetime());
  582. _field->changes(
  583. ) | rpl::start_with_next([=] {
  584. queryChanged();
  585. }, _field->lifetime());
  586. _field->submits(
  587. ) | rpl::start_with_next([=](Qt::KeyboardModifiers m) {
  588. if (_submittedCallback) {
  589. _submittedCallback(m);
  590. }
  591. }, _field->lifetime());
  592. _field->cancelled(
  593. ) | rpl::start_with_next([=] {
  594. cancelled();
  595. }, _field->lifetime());
  596. _cancel->setClickedCallback([=] {
  597. clearQuery();
  598. _field->setFocus();
  599. });
  600. setMouseTracking(true);
  601. }
  602. void MultiSelect::Inner::queryChanged() {
  603. auto query = getQuery();
  604. _cancel->toggle(!query.isEmpty(), anim::type::normal);
  605. updateFieldGeometry();
  606. if (_queryChangedCallback) {
  607. _queryChangedCallback(query);
  608. }
  609. }
  610. QString MultiSelect::Inner::getQuery() const {
  611. return _field->getLastText().trimmed();
  612. }
  613. void MultiSelect::Inner::setQuery(const QString &query) {
  614. _field->setText(query);
  615. if (const auto last = _field->getLastText(); !last.isEmpty()) {
  616. _field->setCursorPosition(last.size());
  617. }
  618. }
  619. bool MultiSelect::Inner::setInnerFocus() {
  620. if (_active >= 0) {
  621. setFocus();
  622. } else if (!_field->hasFocus()) {
  623. _field->setFocusFast();
  624. return true;
  625. }
  626. return false;
  627. }
  628. void MultiSelect::Inner::clearQuery() {
  629. _field->setText(QString());
  630. }
  631. void MultiSelect::Inner::setQueryChangedCallback(Fn<void(const QString &query)> callback) {
  632. _queryChangedCallback = std::move(callback);
  633. }
  634. void MultiSelect::Inner::setSubmittedCallback(
  635. Fn<void(Qt::KeyboardModifiers)> callback) {
  636. _submittedCallback = std::move(callback);
  637. }
  638. void MultiSelect::Inner::setCancelledCallback(Fn<void()> callback) {
  639. _cancelledCallback = std::move(callback);
  640. }
  641. void MultiSelect::Inner::updateFieldGeometry() {
  642. auto fieldFinalWidth = _fieldWidth;
  643. if (_cancel->toggled()) {
  644. fieldFinalWidth -= _st.fieldCancelSkip;
  645. }
  646. _field->resizeToWidth(fieldFinalWidth);
  647. _field->moveToLeft(_st.padding.left() + _fieldLeft, _st.padding.top() + _fieldTop);
  648. }
  649. void MultiSelect::Inner::updateHasAnyItems(bool hasAnyItems) {
  650. _field->setPlaceholderHidden(hasAnyItems);
  651. updateCursor();
  652. _iconOpacity.start([this] {
  653. rtlupdate(_st.padding.left(), _st.padding.top(), _st.fieldIcon.width(), _st.fieldIcon.height());
  654. }, hasAnyItems ? 1. : 0., hasAnyItems ? 0. : 1., _st.item.duration);
  655. }
  656. void MultiSelect::Inner::updateCursor() {
  657. setCursor(_items.empty() ? style::cur_text : (_overDelete ? style::cur_pointer : style::cur_default));
  658. }
  659. void MultiSelect::Inner::setActiveItem(int active, ChangeActiveWay skipSetFocus) {
  660. if (_active == active) return;
  661. if (_active >= 0) {
  662. Assert(_active < _items.size());
  663. _items[_active]->setActive(false);
  664. }
  665. _active = active;
  666. if (_active >= 0) {
  667. Assert(_active < _items.size());
  668. _items[_active]->setActive(true);
  669. }
  670. if (skipSetFocus != ChangeActiveWay::SkipSetFocus) {
  671. setInnerFocus();
  672. }
  673. if (_scrollCallback) {
  674. auto rect = (_active >= 0) ? _items[_active]->rect() : _field->geometry().translated(-_st.padding.left(), -_st.padding.top());
  675. _scrollCallback(rect.y(), rect.y() + rect.height() + _st.padding.top() + _st.padding.bottom());
  676. }
  677. update();
  678. }
  679. void MultiSelect::Inner::setActiveItemPrevious() {
  680. if (_active > 0) {
  681. setActiveItem(_active - 1);
  682. } else if (_active < 0 && !_items.empty()) {
  683. setActiveItem(_items.size() - 1);
  684. }
  685. }
  686. void MultiSelect::Inner::setActiveItemNext() {
  687. if (_active >= 0 && _active + 1 < _items.size()) {
  688. setActiveItem(_active + 1);
  689. } else {
  690. setActiveItem(-1);
  691. }
  692. }
  693. int MultiSelect::Inner::resizeGetHeight(int newWidth) {
  694. computeItemsGeometry(newWidth);
  695. updateFieldGeometry();
  696. auto cancelLeft = _fieldLeft + _fieldWidth + _st.padding.right() - _cancel->width();
  697. auto cancelTop = _fieldTop - _st.padding.top();
  698. _cancel->moveToLeft(_st.padding.left() + cancelLeft, _st.padding.top() + cancelTop);
  699. return _field->y() + _field->height() + _st.padding.bottom();
  700. }
  701. void MultiSelect::Inner::paintEvent(QPaintEvent *e) {
  702. Painter p(this);
  703. auto paintRect = e->rect();
  704. p.fillRect(paintRect, _st.bg);
  705. auto offset = QPoint(
  706. style::RightToLeft() ? _st.padding.right() : _st.padding.left(),
  707. _st.padding.top());
  708. p.translate(offset);
  709. paintRect.translate(-offset);
  710. auto outerWidth = width() - _st.padding.left() - _st.padding.right();
  711. auto iconOpacity = _iconOpacity.value(_items.empty() ? 1. : 0.);
  712. if (iconOpacity > 0.) {
  713. p.setOpacity(iconOpacity);
  714. _st.fieldIcon.paint(p, 0, 0, outerWidth);
  715. p.setOpacity(1.);
  716. }
  717. auto checkRect = myrtlrect(paintRect);
  718. auto paintMargins = itemPaintMargins();
  719. for (auto i = _removingItems.begin(), e = _removingItems.end(); i != e;) {
  720. auto &item = *i;
  721. auto itemRect = item->paintArea(outerWidth);
  722. itemRect = itemRect.marginsAdded(paintMargins);
  723. if (checkRect.intersects(itemRect)) {
  724. item->paint(p, outerWidth);
  725. }
  726. if (item->hideFinished()) {
  727. i = _removingItems.erase(i);
  728. e = _removingItems.end();
  729. } else {
  730. ++i;
  731. }
  732. }
  733. for (const auto &item : _items) {
  734. auto itemRect = item->paintArea(outerWidth);
  735. itemRect = itemRect.marginsAdded(paintMargins);
  736. if (checkRect.y() + checkRect.height() <= itemRect.y()) {
  737. break;
  738. } else if (checkRect.intersects(itemRect)) {
  739. item->paint(p, outerWidth);
  740. }
  741. }
  742. }
  743. QMargins MultiSelect::Inner::itemPaintMargins() const {
  744. return {
  745. qMax(_st.itemSkip, _st.padding.left()),
  746. _st.itemSkip,
  747. qMax(_st.itemSkip, _st.padding.right()),
  748. _st.itemSkip,
  749. };
  750. }
  751. void MultiSelect::Inner::leaveEventHook(QEvent *e) {
  752. clearSelection();
  753. }
  754. void MultiSelect::Inner::mouseMoveEvent(QMouseEvent *e) {
  755. updateSelection(e->pos());
  756. }
  757. void MultiSelect::Inner::keyPressEvent(QKeyEvent *e) {
  758. if (_active >= 0) {
  759. Expects(_active < _items.size());
  760. if (e->key() == Qt::Key_Delete || e->key() == Qt::Key_Backspace) {
  761. auto itemId = _items[_active]->id();
  762. setActiveItemNext();
  763. removeItem(itemId);
  764. } else if (e->key() == Qt::Key_Left) {
  765. setActiveItemPrevious();
  766. } else if (e->key() == Qt::Key_Right) {
  767. setActiveItemNext();
  768. } else if (e->key() == Qt::Key_Escape) {
  769. setActiveItem(-1);
  770. } else {
  771. e->ignore();
  772. }
  773. } else if (e->key() == Qt::Key_Left || e->key() == Qt::Key_Backspace) {
  774. setActiveItemPrevious();
  775. } else {
  776. e->ignore();
  777. }
  778. }
  779. void MultiSelect::Inner::cancelled() {
  780. if (_cancelledCallback) {
  781. _cancelledCallback();
  782. }
  783. }
  784. void MultiSelect::Inner::fieldFocused() {
  785. setActiveItem(-1, ChangeActiveWay::SkipSetFocus);
  786. }
  787. void MultiSelect::Inner::updateSelection(QPoint mousePosition) {
  788. auto point = myrtlpoint(mousePosition) - QPoint(_st.padding.left(), _st.padding.right());
  789. auto selected = -1;
  790. for (auto i = 0, count = int(_items.size()); i != count; ++i) {
  791. auto itemRect = _items[i]->rect();
  792. if (itemRect.y() > point.y()) {
  793. break;
  794. } else if (itemRect.contains(point)) {
  795. point -= itemRect.topLeft();
  796. selected = i;
  797. break;
  798. }
  799. }
  800. if (_selected != selected) {
  801. if (_selected >= 0) {
  802. Assert(_selected < _items.size());
  803. _items[_selected]->leaveEvent();
  804. }
  805. _selected = selected;
  806. update();
  807. }
  808. auto overDelete = false;
  809. if (_selected >= 0) {
  810. _items[_selected]->mouseMoveEvent(point);
  811. overDelete = _items[_selected]->isOverDelete();
  812. }
  813. if (_overDelete != overDelete) {
  814. _overDelete = overDelete;
  815. updateCursor();
  816. }
  817. }
  818. void MultiSelect::Inner::mousePressEvent(QMouseEvent *e) {
  819. if (_overDelete) {
  820. Expects(_selected >= 0);
  821. Expects(_selected < _items.size());
  822. removeItem(_items[_selected]->id());
  823. } else if (_selected >= 0) {
  824. setActiveItem(_selected);
  825. } else {
  826. setInnerFocus();
  827. }
  828. }
  829. void MultiSelect::Inner::addItemInBunch(std::unique_ptr<Item> item) {
  830. auto wasEmpty = _items.empty();
  831. item->setUpdateCallback([this, item = item.get()] {
  832. auto itemRect = item->paintArea(width() - _st.padding.left() - _st.padding.top());
  833. itemRect = itemRect.translated(_st.padding.left(), _st.padding.top());
  834. itemRect = itemRect.marginsAdded(itemPaintMargins());
  835. rtlupdate(itemRect);
  836. });
  837. _idsMap.insert(item->id());
  838. _items.push_back(std::move(item));
  839. if (wasEmpty) {
  840. updateHasAnyItems(true);
  841. }
  842. }
  843. void MultiSelect::Inner::finishItemsBunch(AddItemWay way) {
  844. updateItemsGeometry();
  845. if (way != AddItemWay::SkipAnimation) {
  846. _items.back()->showAnimated();
  847. } else {
  848. _field->finishAnimating();
  849. finishHeightAnimation();
  850. }
  851. }
  852. void MultiSelect::Inner::computeItemsGeometry(int newWidth) {
  853. newWidth -= _st.padding.left() + _st.padding.right();
  854. auto itemLeft = 0;
  855. auto itemTop = 0;
  856. auto widthLeft = newWidth;
  857. auto maxVisiblePadding = qMax(_st.padding.left(), _st.padding.right());
  858. for (const auto &item : _items) {
  859. auto itemWidth = item->getWidth();
  860. Assert(itemWidth <= newWidth);
  861. if (itemWidth > widthLeft) {
  862. itemLeft = 0;
  863. itemTop += _st.item.height + _st.itemSkip;
  864. widthLeft = newWidth;
  865. }
  866. item->setPosition(itemLeft, itemTop, newWidth, maxVisiblePadding);
  867. itemLeft += itemWidth + _st.itemSkip;
  868. widthLeft -= itemWidth + _st.itemSkip;
  869. }
  870. auto fieldMinWidth = _st.fieldMinWidth + _st.fieldCancelSkip;
  871. Assert(fieldMinWidth <= newWidth);
  872. if (fieldMinWidth > widthLeft) {
  873. _fieldLeft = 0;
  874. _fieldTop = itemTop + _st.item.height + _st.itemSkip;
  875. } else {
  876. _fieldLeft = itemLeft + (_items.empty() ? _st.fieldIconSkip : 0);
  877. _fieldTop = itemTop;
  878. }
  879. _fieldWidth = newWidth - _fieldLeft;
  880. }
  881. void MultiSelect::Inner::updateItemsGeometry() {
  882. auto newHeight = resizeGetHeight(width());
  883. if (newHeight == _newHeight) return;
  884. _newHeight = newHeight;
  885. _height.start([this] { updateHeightStep(); }, height(), _newHeight, _st.item.duration);
  886. }
  887. void MultiSelect::Inner::updateHeightStep() {
  888. auto newHeight = qRound(_height.value(_newHeight));
  889. if (auto heightDelta = newHeight - height()) {
  890. resize(width(), newHeight);
  891. if (_resizedCallback) {
  892. _resizedCallback(heightDelta);
  893. }
  894. update();
  895. }
  896. }
  897. void MultiSelect::Inner::finishHeightAnimation() {
  898. _height.stop();
  899. updateHeightStep();
  900. }
  901. void MultiSelect::Inner::setItemText(uint64 itemId, const QString &text) {
  902. for (const auto &item : _items) {
  903. if (item->id() == itemId) {
  904. item->setText(text);
  905. updateItemsGeometry();
  906. return;
  907. }
  908. }
  909. }
  910. void MultiSelect::Inner::setItemRemovedCallback(Fn<void(uint64 itemId)> callback) {
  911. _itemRemovedCallback = std::move(callback);
  912. }
  913. void MultiSelect::Inner::setResizedCallback(Fn<void(int heightDelta)> callback) {
  914. _resizedCallback = std::move(callback);
  915. }
  916. void MultiSelect::Inner::removeItem(uint64 itemId) {
  917. auto found = false;
  918. for (auto i = 0, count = int(_items.size()); i != count; ++i) {
  919. auto &item = _items[i];
  920. if (item->id() == itemId) {
  921. found = true;
  922. clearSelection();
  923. item->hideAnimated();
  924. _idsMap.erase(item->id());
  925. _removingItems.insert(std::move(item));
  926. _items.erase(_items.begin() + i);
  927. if (_active == i) {
  928. _active = -1;
  929. } else if (_active > i) {
  930. --_active;
  931. }
  932. updateItemsGeometry();
  933. if (_items.empty()) {
  934. updateHasAnyItems(false);
  935. }
  936. auto point = QCursor::pos();
  937. if (auto parent = parentWidget()) {
  938. if (parent->rect().contains(parent->mapFromGlobal(point))) {
  939. updateSelection(mapFromGlobal(point));
  940. }
  941. }
  942. break;
  943. }
  944. }
  945. if (found && _itemRemovedCallback) {
  946. _itemRemovedCallback(itemId);
  947. }
  948. setInnerFocus();
  949. }
  950. int MultiSelect::Inner::getItemsCount() const {
  951. return _items.size();
  952. }
  953. QVector<uint64> MultiSelect::Inner::getItems() const {
  954. auto result = QVector<uint64>();
  955. result.reserve(_items.size());
  956. for (const auto &item : _items) {
  957. result.push_back(item->id());
  958. }
  959. return result;
  960. }
  961. bool MultiSelect::Inner::hasItem(uint64 itemId) const {
  962. return _idsMap.find(itemId) != _idsMap.cend();
  963. }
  964. MultiSelect::Inner::~Inner() = default;
  965. } // namespace Ui