choose_font_box.cpp 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967
  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/boxes/choose_font_box.h"
  8. #include "base/event_filter.h"
  9. #include "lang/lang_keys.h"
  10. #include "ui/boxes/confirm_box.h"
  11. #include "ui/chat/chat_style.h"
  12. #include "ui/effects/ripple_animation.h"
  13. #include "ui/layers/generic_box.h"
  14. #include "ui/style/style_core_font.h"
  15. #include "ui/widgets/checkbox.h"
  16. #include "ui/widgets/multi_select.h"
  17. #include "ui/widgets/scroll_area.h"
  18. #include "ui/cached_round_corners.h"
  19. #include "ui/painter.h"
  20. #include "ui/ui_utility.h"
  21. #include "styles/style_boxes.h"
  22. #include "styles/style_chat.h"
  23. #include "styles/style_settings.h"
  24. #include "styles/style_layers.h"
  25. #include "styles/style_window.h"
  26. #include <QtGui/QFontDatabase>
  27. namespace Ui {
  28. namespace {
  29. constexpr auto kMinTextWidth = 120;
  30. constexpr auto kMaxTextWidth = 320;
  31. constexpr auto kMaxTextLines = 3;
  32. struct PreviewRequest {
  33. QString family;
  34. QColor msgBg;
  35. QColor msgShadow;
  36. QColor replyBar;
  37. QColor replyNameFg;
  38. QColor textFg;
  39. QImage bubbleTail;
  40. };
  41. class PreviewPainter {
  42. public:
  43. PreviewPainter(const QImage &bg, PreviewRequest request);
  44. QImage takeResult();
  45. private:
  46. void layout();
  47. void paintBubble(Painter &p);
  48. void paintContent(Painter &p);
  49. void paintReply(Painter &p);
  50. void paintMessage(Painter &p);
  51. void validateBubbleCache();
  52. const PreviewRequest _request;
  53. const style::owned_color _msgBg;
  54. const style::owned_color _msgShadow;
  55. style::owned_font _nameFontOwned;
  56. style::font _nameFont;
  57. style::TextStyle _nameStyle;
  58. style::owned_font _textFontOwned;
  59. style::font _textFont;
  60. style::TextStyle _textStyle;
  61. Ui::Text::String _nameText;
  62. Ui::Text::String _replyText;
  63. Ui::Text::String _messageText;
  64. QRect _replyRect;
  65. QRect _name;
  66. QRect _reply;
  67. QRect _message;
  68. QRect _content;
  69. QRect _bubble;
  70. QSize _outer;
  71. Ui::CornersPixmaps _bubbleCorners;
  72. QPixmap _bubbleShadowBottomRight;
  73. QImage _result;
  74. };
  75. class Selector final : public Ui::RpWidget {
  76. public:
  77. Selector(
  78. not_null<QWidget*> parent,
  79. const QString &now,
  80. rpl::producer<QString> filter,
  81. rpl::producer<> submits,
  82. Fn<void(QString)> chosen,
  83. Fn<void(Ui::ScrollToRequest, anim::type)> scrollTo);
  84. void initScroll(anim::type animated);
  85. void setMinHeight(int height);
  86. void selectSkip(Qt::Key direction);
  87. private:
  88. struct Entry {
  89. QString id;
  90. QString key;
  91. QString text;
  92. QStringList keywords;
  93. QImage cache;
  94. std::unique_ptr<Ui::RadioView> check;
  95. std::unique_ptr<Ui::RippleAnimation> ripple;
  96. int paletteVersion = 0;
  97. };
  98. [[nodiscard]] static std::vector<Entry> FullList(const QString &now);
  99. int resizeGetHeight(int newWidth) override;
  100. void paintEvent(QPaintEvent *e) override;
  101. void leaveEventHook(QEvent *e) override;
  102. void mouseMoveEvent(QMouseEvent *e) override;
  103. void mousePressEvent(QMouseEvent *e) override;
  104. void mouseReleaseEvent(QMouseEvent *e) override;
  105. [[nodiscard]] bool searching() const;
  106. [[nodiscard]] int shownRowsCount() const;
  107. [[nodiscard]] Entry &shownRowAt(int index);
  108. void applyFilter(const QString &query);
  109. void updateSelected(int selected);
  110. void updatePressed(int pressed);
  111. void updateRow(int index);
  112. void updateRow(not_null<Entry*> row, int hint);
  113. void addRipple(int index, QPoint position);
  114. void validateCache(Entry &row);
  115. void choose(Entry &row);
  116. const style::SettingsButton &_st;
  117. std::vector<Entry> _rows;
  118. std::vector<not_null<Entry*>> _filtered;
  119. QString _chosen;
  120. int _selected = -1;
  121. int _pressed = -1;
  122. std::optional<QPoint> _lastGlobalPoint;
  123. bool _selectedByKeyboard = false;
  124. Fn<void(QString)> _callback;
  125. Fn<void(Ui::ScrollToRequest, anim::type)> _scrollTo;
  126. int _rowsSkip = 0;
  127. int _rowHeight = 0;
  128. int _minHeight = 0;
  129. QString _query;
  130. QStringList _queryWords;
  131. rpl::lifetime _lifetime;
  132. };
  133. Selector::Selector(
  134. not_null<QWidget*> parent,
  135. const QString &now,
  136. rpl::producer<QString> filter,
  137. rpl::producer<> submits,
  138. Fn<void(QString)> chosen,
  139. Fn<void(Ui::ScrollToRequest, anim::type)> scrollTo)
  140. : RpWidget(parent)
  141. , _st(st::settingsButton)
  142. , _rows(FullList(now))
  143. , _chosen(now)
  144. , _callback(std::move(chosen))
  145. , _scrollTo(std::move(scrollTo))
  146. , _rowsSkip(st::settingsInfoPhotoSkip)
  147. , _rowHeight(_st.height + _st.padding.top() + _st.padding.bottom()) {
  148. setMouseTracking(true);
  149. std::move(filter) | rpl::start_with_next([=](const QString &query) {
  150. applyFilter(query);
  151. }, _lifetime);
  152. std::move(
  153. submits
  154. ) | rpl::start_with_next([=] {
  155. if (_selected >= 0) {
  156. choose(shownRowAt(_selected));
  157. } else if (searching() && !_filtered.empty()) {
  158. choose(*_filtered.front());
  159. }
  160. }, _lifetime);
  161. }
  162. void Selector::applyFilter(const QString &query) {
  163. if (_query == query) {
  164. return;
  165. }
  166. _query = query;
  167. updateSelected(-1);
  168. updatePressed(-1);
  169. _queryWords = TextUtilities::PrepareSearchWords(_query);
  170. const auto skip = [](
  171. const QStringList &haystack,
  172. const QStringList &needles) {
  173. const auto find = [](
  174. const QStringList &haystack,
  175. const QString &needle) {
  176. for (const auto &item : haystack) {
  177. if (item.startsWith(needle)) {
  178. return true;
  179. }
  180. }
  181. return false;
  182. };
  183. for (const auto &needle : needles) {
  184. if (!find(haystack, needle)) {
  185. return true;
  186. }
  187. }
  188. return false;
  189. };
  190. _filtered.clear();
  191. if (!_queryWords.isEmpty()) {
  192. _filtered.reserve(_rows.size());
  193. for (auto &row : _rows) {
  194. if (!skip(row.keywords, _queryWords)) {
  195. _filtered.push_back(&row);
  196. } else {
  197. row.ripple = nullptr;
  198. }
  199. }
  200. }
  201. resizeToWidth(width());
  202. Ui::SendPendingMoveResizeEvents(this);
  203. update();
  204. }
  205. void Selector::updateSelected(int selected) {
  206. if (_selected == selected) {
  207. return;
  208. }
  209. const auto was = (_selected >= 0);
  210. updateRow(_selected);
  211. _selected = selected;
  212. updateRow(_selected);
  213. const auto now = (_selected >= 0);
  214. if (was != now) {
  215. setCursor(now ? style::cur_pointer : style::cur_default);
  216. }
  217. if (_selectedByKeyboard) {
  218. const auto top = (_selected > 0)
  219. ? (_rowsSkip + _selected * _rowHeight)
  220. : 0;
  221. const auto bottom = (_selected > 0)
  222. ? (top + _rowHeight)
  223. : _selected
  224. ? 0
  225. : _rowHeight;
  226. _scrollTo({ top, bottom }, anim::type::instant);
  227. }
  228. }
  229. void Selector::updatePressed(int pressed) {
  230. if (_pressed == pressed) {
  231. return;
  232. } else if (_pressed >= 0) {
  233. if (auto &ripple = shownRowAt(_pressed).ripple) {
  234. ripple->lastStop();
  235. }
  236. }
  237. updateRow(_pressed);
  238. _pressed = pressed;
  239. updateRow(_pressed);
  240. }
  241. void Selector::updateRow(int index) {
  242. if (index >= 0) {
  243. update(0, _rowsSkip + index * _rowHeight, width(), _rowHeight);
  244. }
  245. }
  246. void Selector::updateRow(not_null<Entry*> row, int hint) {
  247. if (hint >= 0 && hint < shownRowsCount() && &shownRowAt(hint) == row) {
  248. updateRow(hint);
  249. } else if (searching()) {
  250. const auto i = ranges::find(_filtered, row);
  251. if (i != end(_filtered)) {
  252. updateRow(int(i - begin(_filtered)));
  253. }
  254. } else {
  255. const auto index = int(row.get() - &_rows[0]);
  256. Assert(index >= 0 && index < _rows.size());
  257. updateRow(index);
  258. }
  259. }
  260. void Selector::validateCache(Entry &row) {
  261. const auto version = style::PaletteVersion();
  262. if (row.cache.isNull()) {
  263. const auto ratio = style::DevicePixelRatio();
  264. row.cache = QImage(
  265. QSize(width(), _rowHeight) * ratio,
  266. QImage::Format_ARGB32_Premultiplied);
  267. row.cache.setDevicePixelRatio(ratio);
  268. } else if (row.paletteVersion == version) {
  269. return;
  270. }
  271. row.paletteVersion = version;
  272. row.cache.fill(Qt::transparent);
  273. auto owned = style::owned_font(row.id, 0, st::boxFontSize);
  274. const auto font = owned.font();
  275. auto p = QPainter(&row.cache);
  276. p.setFont(font);
  277. p.setPen(st::windowFg);
  278. const auto textw = width() - _st.padding.left() - _st.padding.right();
  279. const auto textt = (_rowHeight - font->height) / 2.;
  280. p.drawText(
  281. _st.padding.left(),
  282. textt + font->ascent,
  283. font->elided(row.text, textw));
  284. }
  285. bool Selector::searching() const {
  286. return !_queryWords.isEmpty();
  287. }
  288. int Selector::shownRowsCount() const {
  289. return searching() ? int(_filtered.size()) : int(_rows.size());
  290. }
  291. Selector::Entry &Selector::shownRowAt(int index) {
  292. return searching() ? *_filtered[index] : _rows[index];
  293. }
  294. void Selector::setMinHeight(int height) {
  295. _minHeight = height;
  296. if (_minHeight > 0) {
  297. resizeToWidth(width());
  298. }
  299. }
  300. void Selector::selectSkip(Qt::Key key) {
  301. const auto count = shownRowsCount();
  302. if (key == Qt::Key_Down) {
  303. if (_selected + 1 < count) {
  304. _selectedByKeyboard = true;
  305. updateSelected(_selected + 1);
  306. }
  307. } else if (key == Qt::Key_Up) {
  308. if (_selected >= 0) {
  309. _selectedByKeyboard = true;
  310. updateSelected(_selected - 1);
  311. }
  312. } else if (key == Qt::Key_PageDown) {
  313. const auto change = _minHeight / _rowHeight;
  314. if (_selected + 1 < count) {
  315. _selectedByKeyboard = true;
  316. updateSelected(std::min(_selected + change, count - 1));
  317. }
  318. } else if (key == Qt::Key_PageUp) {
  319. const auto change = _minHeight / _rowHeight;
  320. if (_selected > 0) {
  321. _selectedByKeyboard = true;
  322. updateSelected(std::max(_selected - change, 0));
  323. } else if (!_selected) {
  324. _selectedByKeyboard = true;
  325. updateSelected(-1);
  326. }
  327. }
  328. }
  329. void Selector::initScroll(anim::type animated) {
  330. const auto index = [&] {
  331. if (searching()) {
  332. const auto i = ranges::find(_filtered, _chosen, &Entry::id);
  333. if (i != end(_filtered)) {
  334. return int(i - begin(_filtered));
  335. }
  336. return -1;
  337. }
  338. const auto i = ranges::find(_rows, _chosen, &Entry::id);
  339. Assert(i != end(_rows));
  340. return int(i - begin(_rows));
  341. }();
  342. if (index >= 0) {
  343. const auto top = _rowsSkip + index * _rowHeight;
  344. const auto use = std::max(top - (_minHeight - _rowHeight) / 2, 0);
  345. _scrollTo({ use, use + _minHeight }, animated);
  346. }
  347. }
  348. int Selector::resizeGetHeight(int newWidth) {
  349. const auto added = 2 * _rowsSkip;
  350. return std::max(added + shownRowsCount() * _rowHeight, _minHeight);
  351. }
  352. void Selector::paintEvent(QPaintEvent *e) {
  353. auto p = QPainter(this);
  354. const auto rows = shownRowsCount();
  355. if (!rows) {
  356. p.setFont(st::normalFont);
  357. p.setPen(st::windowSubTextFg);
  358. p.drawText(
  359. QRect(0, 0, width(), height() * 2 / 3),
  360. tr::lng_font_not_found(tr::now),
  361. style::al_center);
  362. return;
  363. }
  364. const auto clip = e->rect();
  365. const auto clipped = std::max(clip.y() - _rowsSkip, 0);
  366. const auto from = std::min(clipped / _rowHeight, rows);
  367. const auto till = std::min(
  368. (clip.y() + clip.height() - _rowsSkip + _rowHeight - 1) / _rowHeight,
  369. rows);
  370. const auto active = (_pressed >= 0) ? _pressed : _selected;
  371. for (auto i = from; i != till; ++i) {
  372. auto &row = shownRowAt(i);
  373. const auto y = _rowsSkip + i * _rowHeight;
  374. const auto bg = (i == active) ? st::windowBgOver : st::windowBg;
  375. const auto rect = QRect(0, y, width(), _rowHeight);
  376. p.fillRect(rect, bg);
  377. if (row.ripple) {
  378. row.ripple->paint(p, 0, y, width());
  379. if (row.ripple->empty()) {
  380. row.ripple = nullptr;
  381. }
  382. }
  383. validateCache(row);
  384. p.drawImage(0, y, row.cache);
  385. if (!row.check) {
  386. row.check = std::make_unique<Ui::RadioView>(
  387. st::langsRadio,
  388. (row.id == _chosen),
  389. [=, row = &row] { updateRow(row, i); });
  390. }
  391. row.check->paint(
  392. p,
  393. _st.iconLeft,
  394. y + (_rowHeight - st::langsRadio.diameter) / 2,
  395. width());
  396. }
  397. }
  398. void Selector::leaveEventHook(QEvent *e) {
  399. _lastGlobalPoint = std::nullopt;
  400. if (!_selectedByKeyboard) {
  401. updateSelected(-1);
  402. }
  403. }
  404. void Selector::mouseMoveEvent(QMouseEvent *e) {
  405. if (!_lastGlobalPoint) {
  406. _lastGlobalPoint = e->globalPos();
  407. if (_selectedByKeyboard) {
  408. return;
  409. }
  410. } else if (*_lastGlobalPoint == e->globalPos() && _selectedByKeyboard) {
  411. return;
  412. } else {
  413. _lastGlobalPoint = e->globalPos();
  414. }
  415. _selectedByKeyboard = false;
  416. const auto y = e->y() - _rowsSkip;
  417. const auto index = (y >= 0) ? (y / _rowHeight) : -1;
  418. updateSelected((index >= 0 && index < shownRowsCount()) ? index : -1);
  419. }
  420. void Selector::mousePressEvent(QMouseEvent *e) {
  421. updatePressed(_selected);
  422. if (_pressed >= 0) {
  423. addRipple(_pressed, e->pos());
  424. }
  425. }
  426. void Selector::mouseReleaseEvent(QMouseEvent *e) {
  427. const auto pressed = _pressed;
  428. updatePressed(-1);
  429. if (pressed >= 0 && pressed == _selected) {
  430. choose(shownRowAt(pressed));
  431. }
  432. }
  433. void Selector::choose(Entry &row) {
  434. const auto id = row.id;
  435. if (_chosen != id) {
  436. const auto i = ranges::find(_rows, _chosen, &Entry::id);
  437. Assert(i != end(_rows));
  438. if (i->check) {
  439. i->check->setChecked(false, anim::type::normal);
  440. }
  441. _chosen = id;
  442. if (row.check) {
  443. row.check->setChecked(true, anim::type::normal);
  444. }
  445. }
  446. const auto animated = searching()
  447. ? anim::type::instant
  448. : anim::type::normal;
  449. _callback(id);
  450. initScroll(animated);
  451. }
  452. void Selector::addRipple(int index, QPoint position) {
  453. Expects(index >= 0 && index < shownRowsCount());
  454. const auto row = &shownRowAt(index);
  455. if (!row->ripple) {
  456. row->ripple = std::make_unique<Ui::RippleAnimation>(
  457. st::defaultRippleAnimation,
  458. Ui::RippleAnimation::RectMask({ width(), _rowHeight }),
  459. [=] { updateRow(row, index); });
  460. }
  461. row->ripple->add(position - QPoint(0, _rowsSkip + index * _rowHeight));
  462. }
  463. std::vector<Selector::Entry> Selector::FullList(const QString &now) {
  464. using namespace TextUtilities;
  465. auto database = QFontDatabase();
  466. auto families = database.families();
  467. auto result = std::vector<Entry>();
  468. result.reserve(families.size() + 3);
  469. const auto add = [&](const QString &text, const QString &id = {}) {
  470. result.push_back({
  471. .id = id,
  472. .text = text,
  473. .keywords = PrepareSearchWords(text),
  474. });
  475. };
  476. add(tr::lng_font_default(tr::now));
  477. add(tr::lng_font_system(tr::now), style::SystemFontTag());
  478. for (const auto &family : families) {
  479. if (database.isScalable(family)) {
  480. result.push_back({ .id = family });
  481. }
  482. }
  483. auto nowIt = ranges::find(result, now, &Entry::id);
  484. if (nowIt == end(result)) {
  485. result.push_back({ .id = now });
  486. nowIt = end(result) - 1;
  487. }
  488. for (auto i = begin(result) + 2; i != end(result); ++i) {
  489. i->key = TextUtilities::RemoveAccents(i->id).toLower();
  490. i->text = i->id;
  491. i->keywords = TextUtilities::PrepareSearchWords(i->id);
  492. }
  493. auto skip = 2;
  494. if (nowIt - begin(result) >= skip) {
  495. std::swap(result[2], *nowIt);
  496. ++skip;
  497. }
  498. ranges::sort(
  499. begin(result) + skip, end(result),
  500. std::less<>(),
  501. &Entry::key);
  502. return result;
  503. }
  504. [[nodiscard]] PreviewRequest PrepareRequest(const QString &family) {
  505. return {
  506. .family = family,
  507. .msgBg = st::msgInBg->c,
  508. .msgShadow = st::msgInShadow->c,
  509. .replyBar = st::msgInReplyBarColor->c,
  510. .replyNameFg = st::msgInServiceFg->c,
  511. .textFg = st::historyTextInFg->c,
  512. .bubbleTail = st::historyBubbleTailInLeft.instance(st::msgInBg->c),
  513. };
  514. }
  515. PreviewPainter::PreviewPainter(const QImage &bg, PreviewRequest request)
  516. : _request(request)
  517. , _msgBg(_request.msgBg)
  518. , _msgShadow(_request.msgShadow)
  519. , _nameFontOwned(_request.family, style::FontFlag::Semibold, st::fsize)
  520. , _nameFont(_nameFontOwned.font())
  521. , _nameStyle(st::semiboldTextStyle)
  522. , _textFontOwned(_request.family, 0, st::fsize)
  523. , _textFont(_textFontOwned.font())
  524. , _textStyle(st::defaultTextStyle) {
  525. _nameStyle.font = _nameFont;
  526. _textStyle.font = _textFont;
  527. layout();
  528. const auto ratio = style::DevicePixelRatio();
  529. _result = QImage(
  530. _outer * ratio,
  531. QImage::Format_ARGB32_Premultiplied);
  532. _result.setDevicePixelRatio(ratio);
  533. auto p = Painter(&_result);
  534. p.drawImage(0, 0, bg);
  535. p.translate(_bubble.topLeft());
  536. paintBubble(p);
  537. }
  538. void PreviewPainter::paintBubble(Painter &p) {
  539. validateBubbleCache();
  540. const auto bubble = QRect(QPoint(), _bubble.size());
  541. const auto cornerShadow = _bubbleShadowBottomRight.size()
  542. / _bubbleShadowBottomRight.devicePixelRatio();
  543. p.drawPixmap(
  544. bubble.width() - cornerShadow.width(),
  545. bubble.height() + st::msgShadow - cornerShadow.height(),
  546. _bubbleShadowBottomRight);
  547. Ui::FillRoundRect(p, bubble, _msgBg.color(), _bubbleCorners);
  548. const auto &bubbleTail = _request.bubbleTail;
  549. const auto tail = bubbleTail.size() / bubbleTail.devicePixelRatio();
  550. p.drawImage(-tail.width(), bubble.height() - tail.height(), bubbleTail);
  551. p.fillRect(
  552. -tail.width(),
  553. bubble.height(),
  554. tail.width() + bubble.width() - cornerShadow.width(),
  555. st::msgShadow,
  556. _request.msgShadow);
  557. p.translate(_content.topLeft());
  558. const auto local = _content.translated(-_content.topLeft());
  559. p.setClipRect(local);
  560. paintContent(p);
  561. }
  562. void PreviewPainter::validateBubbleCache() {
  563. if (!_bubbleCorners.p[0].isNull()) {
  564. return;
  565. }
  566. const auto radius = st::bubbleRadiusLarge;
  567. _bubbleCorners = Ui::PrepareCornerPixmaps(radius, _msgBg.color());
  568. _bubbleCorners.p[2] = {};
  569. _bubbleShadowBottomRight
  570. = Ui::PrepareCornerPixmaps(radius, _msgShadow.color()).p[3];
  571. }
  572. void PreviewPainter::paintContent(Painter &p) {
  573. paintReply(p);
  574. p.translate(_message.topLeft());
  575. const auto local = _message.translated(-_message.topLeft());
  576. p.setClipRect(local);
  577. paintMessage(p);
  578. }
  579. void PreviewPainter::paintReply(Painter &p) {
  580. {
  581. auto hq = PainterHighQualityEnabler(p);
  582. p.setPen(Qt::NoPen);
  583. p.setBrush(_request.replyBar);
  584. const auto outline = st::messageTextStyle.blockquote.outline;
  585. const auto radius = st::messageTextStyle.blockquote.radius;
  586. p.setOpacity(Ui::kDefaultOutline1Opacity);
  587. p.setClipRect(
  588. _replyRect.x(),
  589. _replyRect.y(),
  590. outline,
  591. _replyRect.height());
  592. p.drawRoundedRect(_replyRect, radius, radius);
  593. p.setOpacity(Ui::kDefaultBgOpacity);
  594. p.setClipRect(
  595. _replyRect.x() + outline,
  596. _replyRect.y(),
  597. _replyRect.width() - outline,
  598. _replyRect.height());
  599. p.drawRoundedRect(_replyRect, radius, radius);
  600. }
  601. p.setOpacity(1.);
  602. p.setClipping(false);
  603. p.setPen(_request.replyNameFg);
  604. _nameText.drawLeftElided(
  605. p,
  606. _name.x(),
  607. _name.y(),
  608. _name.width(),
  609. _outer.width());
  610. p.setPen(_request.textFg);
  611. _replyText.drawLeftElided(
  612. p,
  613. _reply.x(),
  614. _reply.y(),
  615. _reply.width(),
  616. _outer.width());
  617. }
  618. void PreviewPainter::paintMessage(Painter &p) {
  619. p.setPen(_request.textFg);
  620. _messageText.drawLeft(p, 0, 0, _message.width(), _message.width());
  621. }
  622. QImage PreviewPainter::takeResult() {
  623. return std::move(_result);
  624. }
  625. void PreviewPainter::layout() {
  626. const auto skip = st::boxRowPadding.left();
  627. const auto minTextWidth = style::ConvertScale(kMinTextWidth);
  628. const auto maxTextWidth = st::boxWidth
  629. - 2 * skip
  630. - st::msgPadding.left()
  631. - st::msgPadding.right();
  632. _nameText = Ui::Text::String(
  633. _nameStyle,
  634. tr::lng_settings_chat_message_reply_from(tr::now));
  635. _replyText = Ui::Text::String(
  636. _textStyle,
  637. tr::lng_background_text2(tr::now));
  638. _messageText = Ui::Text::String(
  639. _textStyle,
  640. tr::lng_background_text1(tr::now),
  641. kDefaultTextOptions,
  642. st::msgMinWidth / 2);
  643. const auto namePosition = QPoint(
  644. st::historyReplyPadding.left(),
  645. st::historyReplyPadding.top());
  646. const auto replyPosition = QPoint(
  647. st::historyReplyPadding.left(),
  648. (st::historyReplyPadding.top() + _nameFont->height));
  649. const auto paddingRight = st::historyReplyPadding.right();
  650. const auto wantedWidth = std::max({
  651. namePosition.x() + _nameText.maxWidth() + paddingRight,
  652. replyPosition.x() + _replyText.maxWidth() + paddingRight,
  653. _messageText.maxWidth()
  654. });
  655. const auto messageWidth = std::clamp(
  656. wantedWidth,
  657. minTextWidth,
  658. maxTextWidth);
  659. const auto messageHeight = _messageText.countHeight(messageWidth);
  660. _replyRect = QRect(
  661. st::msgReplyBarPos.x(),
  662. st::historyReplyTop,
  663. messageWidth,
  664. (st::historyReplyPadding.top()
  665. + _nameFont->height
  666. + _textFont->height
  667. + st::historyReplyPadding.bottom()));
  668. _name = QRect(
  669. _replyRect.topLeft() + namePosition,
  670. QSize(messageWidth - namePosition.x(), _nameFont->height));
  671. _reply = QRect(
  672. _replyRect.topLeft() + replyPosition,
  673. QSize(messageWidth - replyPosition.x(), _textFont->height));
  674. _message = QRect(0, 0, messageWidth, messageHeight);
  675. const auto replySkip = _replyRect.y()
  676. + _replyRect.height()
  677. + st::historyReplyBottom;
  678. _message.moveTop(replySkip);
  679. _content = QRect(0, 0, messageWidth, replySkip + messageHeight);
  680. const auto msgPadding = st::msgPadding;
  681. _bubble = _content.marginsAdded(msgPadding);
  682. _content.moveTopLeft(-_bubble.topLeft());
  683. _bubble.moveTopLeft({});
  684. _outer = QSize(st::boxWidth, st::boxWidth / 2);
  685. _bubble.moveTopLeft({ skip, std::max(
  686. (_outer.height() - _bubble.height()) / 2,
  687. st::msgMargin.top()) });
  688. }
  689. [[nodiscard]] QImage GeneratePreview(
  690. const QImage &bg,
  691. PreviewRequest request) {
  692. return PreviewPainter(bg, request).takeResult();
  693. }
  694. [[nodiscard]] object_ptr<Ui::RpWidget> MakePreview(
  695. not_null<QWidget*> parent,
  696. Fn<QImage()> generatePreviewBg,
  697. rpl::producer<QString> family) {
  698. auto result = object_ptr<Ui::RpWidget>(parent.get());
  699. const auto raw = result.data();
  700. struct State {
  701. QImage preview;
  702. QImage bg;
  703. QString family;
  704. };
  705. const auto state = raw->lifetime().make_state<State>();
  706. state->bg = generatePreviewBg();
  707. style::PaletteChanged() | rpl::start_with_next([=] {
  708. state->bg = generatePreviewBg();
  709. }, raw->lifetime());
  710. rpl::combine(
  711. rpl::single(rpl::empty) | rpl::then(style::PaletteChanged()),
  712. std::move(family)
  713. ) | rpl::start_with_next([=](const auto &, QString family) {
  714. state->family = family;
  715. if (state->preview.isNull()) {
  716. state->preview = GeneratePreview(
  717. state->bg,
  718. PrepareRequest(family));
  719. const auto ratio = state->preview.devicePixelRatio();
  720. raw->resize(state->preview.size() / int(ratio));
  721. } else {
  722. const auto weak = Ui::MakeWeak(raw);
  723. const auto request = PrepareRequest(family);
  724. crl::async([=, bg = state->bg] {
  725. crl::on_main([
  726. weak,
  727. state,
  728. preview = GeneratePreview(bg, request)
  729. ]() mutable {
  730. if (const auto strong = weak.data()) {
  731. state->preview = std::move(preview);
  732. const auto ratio = state->preview.devicePixelRatio();
  733. strong->resize(
  734. strong->width(),
  735. (state->preview.height() / int(ratio)));
  736. strong->update();
  737. }
  738. });
  739. });
  740. }
  741. }, raw->lifetime());
  742. raw->paintRequest() | rpl::start_with_next([=](QRect clip) {
  743. QPainter(raw).drawImage(0, 0, state->preview);
  744. }, raw->lifetime());
  745. return result;
  746. }
  747. } // namespace
  748. void ChooseFontBox(
  749. not_null<GenericBox*> box,
  750. Fn<QImage()> generatePreviewBg,
  751. const QString &family,
  752. Fn<void(QString)> save) {
  753. box->setTitle(tr::lng_font_box_title());
  754. struct State {
  755. rpl::variable<QString> family;
  756. rpl::variable<QString> query;
  757. rpl::event_stream<> submits;
  758. };
  759. const auto state = box->lifetime().make_state<State>(State{
  760. .family = family,
  761. });
  762. const auto top = box->setPinnedToTopContent(
  763. object_ptr<Ui::VerticalLayout>(box));
  764. top->add(MakePreview(top, generatePreviewBg, state->family.value()));
  765. const auto filter = top->add(object_ptr<Ui::MultiSelect>(
  766. top,
  767. st::defaultMultiSelect,
  768. tr::lng_participant_filter()));
  769. top->resizeToWidth(st::boxWidth);
  770. filter->setSubmittedCallback([=](Qt::KeyboardModifiers) {
  771. state->submits.fire({});
  772. });
  773. filter->setQueryChangedCallback([=](const QString &query) {
  774. state->query = query;
  775. });
  776. filter->setCancelledCallback([=] {
  777. filter->clearQuery();
  778. });
  779. const auto chosen = [=](const QString &value) {
  780. state->family = value;
  781. filter->clearQuery();
  782. };
  783. const auto scrollTo = [=](
  784. Ui::ScrollToRequest request,
  785. anim::type animated) {
  786. box->scrollTo(request, animated);
  787. };
  788. const auto selector = box->addRow(
  789. object_ptr<Selector>(
  790. box,
  791. state->family.current(),
  792. state->query.value(),
  793. state->submits.events(),
  794. chosen,
  795. scrollTo),
  796. QMargins());
  797. box->setMinHeight(st::boxMaxListHeight);
  798. box->setMaxHeight(st::boxMaxListHeight);
  799. base::install_event_filter(filter, [=](not_null<QEvent*> e) {
  800. if (e->type() == QEvent::KeyPress) {
  801. const auto key = static_cast<QKeyEvent*>(e.get())->key();
  802. if (key == Qt::Key_Up
  803. || key == Qt::Key_Down
  804. || key == Qt::Key_PageUp
  805. || key == Qt::Key_PageDown) {
  806. selector->selectSkip(Qt::Key(key));
  807. return base::EventFilterResult::Cancel;
  808. }
  809. }
  810. return base::EventFilterResult::Continue;
  811. });
  812. rpl::combine(
  813. box->heightValue(),
  814. top->heightValue()
  815. ) | rpl::start_with_next([=](int box, int top) {
  816. selector->setMinHeight(box - top);
  817. }, selector->lifetime());
  818. const auto apply = [=](QString chosen) {
  819. if (chosen == family) {
  820. box->closeBox();
  821. return;
  822. }
  823. box->getDelegate()->show(Ui::MakeConfirmBox({
  824. .text = tr::lng_settings_need_restart(),
  825. .confirmed = [=] { save(chosen); },
  826. .confirmText = tr::lng_settings_restart_now(),
  827. }));
  828. };
  829. const auto refreshButtons = [=](QString chosen) {
  830. box->clearButtons();
  831. // Doesn't fit in most languages.
  832. //if (!chosen.isEmpty()) {
  833. // box->addLeftButton(tr::lng_background_reset_default(), [=] {
  834. // apply(QString());
  835. // });
  836. //}
  837. box->addButton(tr::lng_settings_save(), [=] {
  838. apply(chosen);
  839. });
  840. box->addButton(tr::lng_cancel(), [=] {
  841. box->closeBox();
  842. });
  843. };
  844. state->family.value(
  845. ) | rpl::start_with_next(refreshButtons, box->lifetime());
  846. box->setFocusCallback([=] {
  847. filter->setInnerFocus();
  848. });
  849. box->setInitScrollCallback([=] {
  850. SendPendingMoveResizeEvents(box);
  851. selector->initScroll(anim::type::instant);
  852. });
  853. }
  854. } // namespace Ui