From 2c6f74f923feed04d1e7874d71af8965e64db4c3 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Mon, 28 Mar 2016 20:15:17 +0300 Subject: [PATCH] Reply markup keyboard class almost ready, single class for inline and external bot keyboard handling. But it needs to reinvent a good improvement/replacement for ITextLink concept that will support automatic calls of linkOver()/linkOut() methods in all link holders. --- Telegram/SourceFiles/history.cpp | 242 +++++++++++++++-- Telegram/SourceFiles/history.h | 107 +++++++- Telegram/SourceFiles/historywidget.cpp | 287 ++++++-------------- Telegram/SourceFiles/historywidget.h | 42 +-- Telegram/SourceFiles/mtproto/connection.cpp | 2 +- Telegram/SourceFiles/types.h | 83 +++++- 6 files changed, 509 insertions(+), 254 deletions(-) diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index 155081491..b123023be 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -2705,6 +2705,192 @@ void HistoryBlock::removeItem(HistoryItem *item) { } } +ReplyKeyboard::ReplyKeyboard(const HistoryItem *item, StylePtr &&s) +: _item(item) +, _a_selected(animation(this, &ReplyKeyboard::step_selected)) +, _st(std_::forward<StylePtr>(s)) { + if (auto *markup = item->Get<HistoryMessageReplyMarkup>()) { + _rows.reserve(markup->rows.size()); + for (int i = 0, l = markup->rows.size(); i != l; ++i) { + const HistoryMessageReplyMarkup::ButtonRow &row(markup->rows.at(i)); + int s = row.size(); + ButtonRow newRow(s, Button()); + for (int j = 0; j != s; ++j) { + Button &button(newRow[j]); + QString str = row.at(j).text; + button.link.reset(new TextLink(qsl("https://telegram.org"))); + button.text.setText(_st->textFont(), textOneLine(str), _textPlainOptions); + button.characters = str.isEmpty() ? 1 : str.size(); + } + _rows.push_back(newRow); + } + } +} + +void ReplyKeyboard::resize(int width, int height) { + _width = width; + + auto *markup = _item->Get<HistoryMessageReplyMarkup>(); + float64 y = 0, buttonHeight = _rows.isEmpty() ? _st->buttonHeight() : (float64(height + _st->buttonSkip()) / _rows.size()); + for (ButtonRow &row : _rows) { + int s = row.size(); + + float64 widthForText = _width - ((s - 1) * _st->buttonSkip() + s * 2 * _st->buttonPadding()), widthOfText = 0.; + for_const (const Button &button, row) { + widthOfText += qMax(button.text.maxWidth(), 1); + } + + float64 x = 0, coef = widthForText / widthOfText; + for (Button &button : row) { + float64 tw = widthForText / float64(s), w = 2 * _st->buttonPadding() + tw; + if (w < _st->buttonPadding()) w = _st->buttonPadding(); + + button.rect = QRect(qRound(x), qRound(y), qRound(w), qRound(buttonHeight - _st->buttonSkip())); + x += w + _st->buttonSkip(); + + button.full = (tw >= button.text.maxWidth()); + } + y += buttonHeight; + } +} + +bool ReplyKeyboard::isEnoughSpace(int width, const style::botKeyboardButton &st) const { + for_const (const ButtonRow &row, _rows) { + int s = row.size(); + int widthLeft = width - ((s - 1) * st.margin + s * 2 * st.padding); + for_const (const Button &button, row) { + widthLeft -= qMax(button.text.maxWidth(), 1); + if (widthLeft < 0) { + if (row.size() > 3) { + return false; + } else { + break; + } + } + } + } + return true; +} + +void ReplyKeyboard::setStyle(StylePtr &&st) { + _st = std_::move(st); +} + +int ReplyKeyboard::naturalHeight() const { + return (_rows.size() - 1) * _st->buttonSkip() + _rows.size() * _st->buttonHeight(); +} + +void ReplyKeyboard::paint(Painter &p, const QRect &clip) const { + t_assert(!_st.isNull()); + t_assert(_width > 0); + + _st->startPaint(p); + for_const (const ButtonRow &row, _rows) { + for_const (const Button &button, row) { + QRect rect(button.rect); + if (rect.y() >= clip.y() + clip.height()) return; + if (rect.y() + rect.height() < clip.y()) continue; + + if (rtl()) rect.moveLeft(_width - rect.left() - rect.width()); + + bool down = (textlnkDown() == button.link); + float64 howMuchOver = button.howMuchOver; + _st->paintButton(p, rect, button.text, down, howMuchOver); + } + } +} + +void ReplyKeyboard::getState(TextLinkPtr &lnk, int x, int y) const { + t_assert(_width > 0); + + lnk.reset(); + for_const(const ButtonRow &row, _rows) { + for_const(const Button &button, row) { + QRect rect(button.rect); + + if (rtl()) rect.moveLeft(_width - rect.left() - rect.width()); + + if (rect.contains(x, y)) { + lnk = button.link; + return; + } + } + } +} + +void ReplyKeyboard::linkOver(const TextLinkPtr &lnk) { + /*if (newSel != _sel) { + if (newSel < 0) { + setCursor(style::cur_default); + } else if (_sel < 0) { + setCursor(style::cur_pointer); + } + bool startanim = false; + if (_sel >= 0) { + _animations.remove(_sel + 1); + if (_animations.find(-_sel - 1) == _animations.end()) { + if (_animations.isEmpty()) startanim = true; + _animations.insert(-_sel - 1, getms()); + } + } + _sel = newSel; + if (_sel >= 0) { + _animations.remove(-_sel - 1); + if (_animations.find(_sel + 1) == _animations.end()) { + if (_animations.isEmpty()) startanim = true; + _animations.insert(_sel + 1, getms()); + } + } + if (startanim && !_a_selected.animating()) _a_selected.start(); + }*/ +} + +void ReplyKeyboard::linkOut(const TextLinkPtr &lnk) { + +} + +void ReplyKeyboard::step_selected(uint64 ms, bool timer) { + for (Animations::iterator i = _animations.begin(); i != _animations.end();) { + int index = qAbs(i.key()) - 1, row = (index / MatrixRowShift), col = index % MatrixRowShift; + float64 dt = float64(ms - i.value()) / st::botKbDuration; + if (dt >= 1) { + _rows[row][col].howMuchOver = (i.key() > 0) ? 1 : 0; + i = _animations.erase(i); + } else { + _rows[row][col].howMuchOver = (i.key() > 0) ? dt : (1 - dt); + ++i; + } + } + if (timer) _st->repaint(_item); + if (_animations.isEmpty()) { + _a_selected.stop(); + } +} + +void ReplyKeyboard::clearSelection() { + for (auto i = _animations.cbegin(), e = _animations.cend(); i != e; ++i) { + int index = qAbs(i.key()) - 1, row = (index / MatrixRowShift), col = index % MatrixRowShift; + _rows[row][col].howMuchOver = 0; + } + _animations.clear(); + _a_selected.stop(); +} + +void ReplyKeyboard::Style::paintButton(Painter &p, const QRect &rect, const Text &text, bool down, float64 howMuchOver) const { + paintButtonBg(p, rect, down, howMuchOver); + + int tx = rect.x(), tw = rect.width(); + if (tw > st::botKbFont->elidew + _st->padding * 2) { + tx += _st->padding; + tw -= _st->padding * 2; + } else if (tw > st::botKbFont->elidew) { + tx += (tw - st::botKbFont->elidew) / 2; + tw = st::botKbFont->elidew; + } + int textTop = rect.y() + (down ? _st->downTextTop : _st->textTop); + text.drawElided(p, tx, textTop + ((rect.height() - _st->height) / 2), tw, 1, style::al_top); +} + void HistoryMessageReplyMarkup::create(const MTPReplyMarkup &markup) { switch (markup.type()) { case mtpc_replyKeyboardMarkup: { @@ -6215,6 +6401,22 @@ void HistoryMessageReply::paint(Painter &p, const HistoryItem *holder, int x, in } } +void HistoryMessage::KeyboardStyle::startPaint(Painter &p) const { + p.setPen(st::msgServiceColor); +} + +style::font HistoryMessage::KeyboardStyle::textFont() const { + return st::msgServiceFont; +} + +void HistoryMessage::KeyboardStyle::repaint(const HistoryItem *item) const { + Ui::repaintHistoryItem(item); +} + +void HistoryMessage::KeyboardStyle::paintButtonBg(Painter &p, const QRect &rect, bool down, float64 howMuchOver) const { + App::roundRect(p, rect, App::msgServiceBg(), ServiceCorners); +} + HistoryMessage::HistoryMessage(History *history, const MTPDmessage &msg) : HistoryItem(history, msg.vid.v, msg.vflags.v, ::date(msg.vdate), msg.has_from_id() ? msg.vfrom_id.v : 0) { CreateConfig config; @@ -6513,6 +6715,9 @@ void HistoryMessage::initDimensions() { } } if (HistoryMessageReplyMarkup *markup = inlineReplyMarkup()) { + if (!markup->inlineKeyboard) { + markup->inlineKeyboard = new ReplyKeyboard(this, MakeUnique<KeyboardStyle>(st::msgBotKbButton)); + } } } @@ -6818,14 +7023,13 @@ void HistoryMessage::draw(Painter &p, const QRect &r, uint32 selection, uint64 m textstyleSet(&(outbg ? st::outTextStyle : st::inTextStyle)); - if (auto *markup = inlineReplyMarkup()) { - height -= markup->rows.size() * (st::msgBotKbButton.margin + st::msgBotKbButton.height); - int y = marginTop() + height + st::msgBotKbButton.margin; - for_const (const HistoryMessageReplyMarkup::ButtonRow &row, markup->rows) { - for_const (const HistoryMessageReplyMarkup::Button &button, row) { - - } - } + if (const ReplyKeyboard *keyboard = inlineReplyKeyboard()) { + int h = st::msgBotKbButton.margin + keyboard->naturalHeight(); + height -= h; + int top = marginTop() + height; + p.translate(left, top); + keyboard->paint(p, r.translated(-left, -top)); + p.translate(-left, -top); } auto *reply = Get<HistoryMessageReply>(); @@ -7026,8 +7230,10 @@ int HistoryMessage::resizeGetHeight_(int width) { } else { _height = _media->resize(width, this); } - if (HistoryMessageReplyMarkup *markup = inlineReplyMarkup()) { - _height += (st::msgBotKbButton.margin + st::msgBotKbButton.height) * markup->rows.size(); + if (ReplyKeyboard *keyboard = inlineReplyKeyboard()) { + int h = st::msgBotKbButton.margin + keyboard->naturalHeight(); + _height += h; + keyboard->resize(width, h - st::msgBotKbButton.margin); } _height += marginTop() + marginBottom(); @@ -7072,17 +7278,15 @@ void HistoryMessage::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 int left = 0, width = 0, height = _height; countPositionAndSize(left, width); - //if (displayFromPhoto()) { - // int32 photoleft = left + ((!isPost() && out() && !Adaptive::Wide()) ? (width + (st::msgPhotoSkip - st::msgPhotoSize)) : (-st::msgPhotoSkip)); - // if (x >= photoleft && x < photoleft + st::msgPhotoSize && y >= marginTop() && y < height - marginBottom()) { - // lnk = author()->lnk; - // return; - // } - //} if (width < 1) return; - if (const HistoryMessageReplyMarkup *markup = inlineReplyMarkup()) { - height -= (st::msgBotKbButton.margin + st::msgBotKbButton.height) * markup->rows.size(); + if (const ReplyKeyboard *keyboard = inlineReplyKeyboard()) { + int h = st::msgBotKbButton.margin + keyboard->naturalHeight(); + height -= h; + int top = marginTop() + height; + if (x >= left && x < left + width && y >= top && y < _height - marginBottom()) { + return keyboard->getState(lnk, x - left, y - top); + } } if (drawBubble()) { diff --git a/Telegram/SourceFiles/history.h b/Telegram/SourceFiles/history.h index 5f617c80e..17b643604 100644 --- a/Telegram/SourceFiles/history.h +++ b/Telegram/SourceFiles/history.h @@ -1052,10 +1052,77 @@ struct HistoryMessageReply : public BaseComponent<HistoryMessageReply> { }; Q_DECLARE_OPERATORS_FOR_FLAGS(HistoryMessageReply::PaintFlags); -class ReplyKeyboard { +class ReplyKeyboard final { public: - ReplyKeyboard(HistoryItem *item); + class Style { + public: + Style(const style::botKeyboardButton &st) : _st(&st) { + } + virtual void startPaint(Painter &p) const = 0; + virtual style::font textFont() const = 0; + + void paintButton(Painter &p, const QRect &rect, const Text &text, bool down, float64 howMuchOver) const; + + int buttonSkip() const { + return _st->margin; + } + int buttonPadding() const { + return _st->padding; + } + int buttonHeight() const { + return _st->height; + } + + virtual void repaint(const HistoryItem *item) const = 0; + + protected: + virtual void paintButtonBg(Painter &p, const QRect &rect, bool down, float64 howMuchOver) const = 0; + + private: + const style::botKeyboardButton *_st; + + }; + typedef UniquePointer<Style> StylePtr; + + ReplyKeyboard(const HistoryItem *item, StylePtr &&s); + ReplyKeyboard(const ReplyKeyboard &other) = delete; + ReplyKeyboard &operator=(const ReplyKeyboard &other) = delete; + + bool isEnoughSpace(int width, const style::botKeyboardButton &st) const; + void setStyle(StylePtr &&s); + void resize(int width, int height); + int naturalHeight() const; + + void paint(Painter &p, const QRect &clip) const; + void getState(TextLinkPtr &lnk, int x, int y) const; + + void linkOver(const TextLinkPtr &lnk); + void linkOut(const TextLinkPtr &lnk); + void clearSelection(); + +private: + const HistoryItem *_item; + int _width = 0; + + struct Button { + Text text = { 1 }; + QRect rect; + int characters = 0; + float64 howMuchOver = 0.; + bool full = true; + TextLinkPtr link; + }; + using ButtonRow = QVector<Button>; + using ButtonRows = QVector<ButtonRow>; + ButtonRows _rows; + + using Animations = QMap<int, uint64>; + Animations _animations; + Animation _a_selected; + void step_selected(uint64 ms, bool timer); + + StylePtr _st; }; struct HistoryMessageReplyMarkup : public BaseComponent<HistoryMessageReplyMarkup> { @@ -1303,8 +1370,18 @@ public: return (from << 16) | to; } virtual void linkOver(const TextLinkPtr &lnk) { + if (auto *markup = Get<HistoryMessageReplyMarkup>()) { + if (markup->inlineKeyboard) { + markup->inlineKeyboard->linkOver(lnk); + } + } } virtual void linkOut(const TextLinkPtr &lnk) { + if (auto *markup = Get<HistoryMessageReplyMarkup>()) { + if (markup->inlineKeyboard) { + markup->inlineKeyboard->linkOut(lnk); + } + } } virtual HistoryItemType type() const { return HistoryItemMsg; @@ -1569,9 +1646,18 @@ protected: } return nullptr; } + const ReplyKeyboard *inlineReplyKeyboard() const { + if (auto *markup = inlineReplyMarkup()) { + return markup->inlineKeyboard; + } + return nullptr; + } HistoryMessageReplyMarkup *inlineReplyMarkup() { return const_cast<HistoryMessageReplyMarkup*>(static_cast<const HistoryItem*>(this)->inlineReplyMarkup()); } + ReplyKeyboard *inlineReplyKeyboard() { + return const_cast<ReplyKeyboard*>(static_cast<const HistoryItem*>(this)->inlineReplyKeyboard()); + } }; @@ -2545,9 +2631,11 @@ public: } void linkOver(const TextLinkPtr &lnk) override { if (_media) _media->linkOver(this, lnk); + HistoryItem::linkOver(lnk); } void linkOut(const TextLinkPtr &lnk) override { if (_media) _media->linkOut(this, lnk); + HistoryItem::linkOut(lnk); } void drawInDialog(Painter &p, const QRect &r, bool act, const HistoryItem *&cacheFor, Text &cache) const override; @@ -2676,6 +2764,19 @@ protected: void createComponentsHelper(MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId); void createComponents(const CreateConfig &config); + class KeyboardStyle : public ReplyKeyboard::Style { + public: + using ReplyKeyboard::Style::Style; + + void startPaint(Painter &p) const override; + style::font textFont() const override; + void repaint(const HistoryItem *item) const override; + + protected: + void paintButtonBg(Painter &p, const QRect &rect, bool down, float64 howMuchOver) const override; + + }; + }; inline MTPDmessage::Flags newMessageFlags(PeerData *p) { @@ -2752,9 +2853,11 @@ public: void linkOver(const TextLinkPtr &lnk) override { if (_media) _media->linkOver(this, lnk); + HistoryItem::linkOver(lnk); } void linkOut(const TextLinkPtr &lnk) override { if (_media) _media->linkOut(this, lnk); + HistoryItem::linkOut(lnk); } void drawInDialog(Painter &p, const QRect &r, bool act, const HistoryItem *&cacheFor, Text &cache) const override; diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 618bbd72f..f1f2764be 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -1754,7 +1754,7 @@ void HistoryInner::onUpdateSelected() { } textlnkOver(lnk); PopupTooltip::Hide(); - App::hoveredLinkItem((lnk && !lnkInDesc) ? item : 0); + App::hoveredLinkItem((lnk && !lnkInDesc) ? item : nullptr); if (textlnkOver()) { if (HistoryItem *item = App::hoveredLinkItem()) { item->linkOver(textlnkOver()); @@ -2164,16 +2164,7 @@ void ReportSpamPanel::setReported(bool reported, PeerData *onPeer) { update(); } -BotKeyboard::BotKeyboard() : TWidget() -, _height(0) -, _maxOuterHeight(0) -, _maximizeSize(false) -, _singleUse(false) -, _forceReply(false) -, _sel(-1) -, _down(-1) -, _a_selected(animation(this, &BotKeyboard::step_selected)) -, _st(&st::botKbButton) { +BotKeyboard::BotKeyboard() { setGeometry(0, 0, _st->margin, _st->margin); _height = _st->margin; setMouseTracking(true); @@ -2182,90 +2173,65 @@ BotKeyboard::BotKeyboard() : TWidget() void BotKeyboard::paintEvent(QPaintEvent *e) { Painter p(this); - QRect r(e->rect()); - p.setClipRect(r); - p.fillRect(r, st::white->b); + QRect clip(e->rect()); + p.fillRect(clip, st::white); - p.setPen(st::botKbColor->p); - p.setFont(st::botKbFont->f); - for (int32 i = 0, l = _btns.size(); i != l; ++i) { - int32 j = 0, s = _btns.at(i).size(); - for (; j != s; ++j) { - const Button &btn(_btns.at(i).at(j)); - QRect rect(btn.rect); - if (rect.y() >= r.y() + r.height()) break; - if (rect.y() + rect.height() < r.y()) continue; + if (_impl) { + int x = rtl() ? st::botKbScroll.width : _st->margin; + p.translate(x, _st->margin); + _impl->paint(p, clip.translated(-x, -_st->margin)); + } +} - if (rtl()) rect.moveLeft(width() - rect.left() - rect.width()); +void BotKeyboard::Style::startPaint(Painter &p) const { + p.setPen(st::botKbColor); + p.setFont(st::botKbFont); +} - int32 tx = rect.x(), tw = rect.width(); - if (tw > st::botKbFont->elidew + _st->padding * 2) { - tx += _st->padding; - tw -= _st->padding * 2; - } else if (tw > st::botKbFont->elidew) { - tx += (tw - st::botKbFont->elidew) / 2; - tw = st::botKbFont->elidew; - } - if (_down == i * MatrixRowShift + j) { - App::roundRect(p, rect, st::botKbDownBg, BotKeyboardDownCorners); - btn.text.drawElided(p, tx, rect.y() + _st->downTextTop + ((rect.height() - _st->height) / 2), tw, 1, style::al_top); - } else { - App::roundRect(p, rect, st::botKbBg, BotKeyboardCorners); - float64 hover = btn.hover; - if (hover > 0) { - p.setOpacity(hover); - App::roundRect(p, rect, st::botKbOverBg, BotKeyboardOverCorners); - p.setOpacity(1); - } - btn.text.drawElided(p, tx, rect.y() + _st->textTop + ((rect.height() - _st->height) / 2), tw, 1, style::al_top); - } +style::font BotKeyboard::Style::textFont() const { + return st::botKbFont; +} + +void BotKeyboard::Style::repaint(const HistoryItem *item) const { + _parent->update(); +} + +void BotKeyboard::Style::paintButtonBg(Painter &p, const QRect &rect, bool down, float64 howMuchOver) const { + if (down) { + App::roundRect(p, rect, st::botKbDownBg, BotKeyboardDownCorners); + } else { + App::roundRect(p, rect, st::botKbBg, BotKeyboardCorners); + if (howMuchOver > 0) { + p.setOpacity(howMuchOver); + App::roundRect(p, rect, st::botKbOverBg, BotKeyboardOverCorners); + p.setOpacity(1); } - if (j < s) break; } } void BotKeyboard::resizeEvent(QResizeEvent *e) { updateStyle(); - _height = (_btns.size() + 1) * _st->margin + _btns.size() * _st->height; + _height = _impl->naturalHeight() + 2 * _st->margin; if (_maximizeSize) _height = qMax(_height, _maxOuterHeight); if (height() != _height) { resize(width(), _height); return; } - float64 y = _st->margin, btnh = _btns.isEmpty() ? _st->height : (float64(_height - _st->margin) / _btns.size()); - for (int32 i = 0, l = _btns.size(); i != l; ++i) { - int32 j = 0, s = _btns.at(i).size(); - - float64 widthForText = width() - (s * _st->margin + st::botKbScroll.width + s * 2 * _st->padding), widthOfText = 0.; - for (; j != s; ++j) { - Button &btn(_btns[i][j]); - if (btn.text.isEmpty()) btn.text.setText(st::botKbFont, textOneLine(btn.button.text), _textPlainOptions); - if (!btn.cwidth) btn.cwidth = btn.button.text.size(); - if (!btn.cwidth) btn.cwidth = 1; - widthOfText += qMax(btn.text.maxWidth(), 1); - } - - float64 x = _st->margin, coef = widthForText / widthOfText; - for (j = 0; j != s; ++j) { - Button &btn(_btns[i][j]); - float64 tw = widthForText / float64(s), w = 2 * _st->padding + tw; - if (w < _st->padding) w = _st->padding; - - btn.rect = QRect(qRound(x), qRound(y), qRound(w), qRound(btnh - _st->margin)); - x += w + _st->margin; - - btn.full = tw >= btn.text.maxWidth(); - } - y += btnh; - } + _impl->resize(width() - _st->margin - st::botKbScroll.width, _height - 2 * _st->margin); } void BotKeyboard::mousePressEvent(QMouseEvent *e) { _lastMousePos = e->globalPos(); updateSelected(); - _down = _sel; + if (textlnkDown() != textlnkOver()) { + Ui::repaintHistoryItem(App::pressedLinkItem()); + textlnkDown(textlnkOver()); + App::hoveredLinkItem(nullptr); + App::pressedLinkItem(App::hoveredLinkItem()); + Ui::repaintHistoryItem(App::pressedLinkItem()); + } update(); } @@ -2275,14 +2241,13 @@ void BotKeyboard::mouseMoveEvent(QMouseEvent *e) { } void BotKeyboard::mouseReleaseEvent(QMouseEvent *e) { - int32 down = _down; - _down = -1; + TextLinkPtr down(textlnkDown()); + textlnkDown(TextLinkPtr()); _lastMousePos = e->globalPos(); updateSelected(); - if (_sel == down && down >= 0) { - int row = (down / MatrixRowShift), col = down % MatrixRowShift; - App::activateBotCommand(_btns.at(row).at(col).button, _wasForMsgId.msg); + if (down && textlnkOver() == down) { + down->onClick(e->button()); } } @@ -2297,37 +2262,21 @@ bool BotKeyboard::updateMarkup(HistoryItem *to) { _wasForMsgId = FullMsgId(to->channelId(), to->id); clearSelection(); - _btns.clear(); const auto *markup = to->Get<HistoryMessageReplyMarkup>(); _forceReply = markup->flags & MTPDreplyKeyboardMarkup_ClientFlag::f_force_reply; _maximizeSize = !(markup->flags & MTPDreplyKeyboardMarkup::Flag::f_resize); _singleUse = _forceReply || (markup->flags & MTPDreplyKeyboardMarkup::Flag::f_single_use); - const HistoryMessageReplyMarkup::ButtonRows &rows(markup->rows); - if (!rows.isEmpty()) { - _btns.reserve(rows.size()); - for_const (const HistoryMessageReplyMarkup::ButtonRow &row, rows) { - QList<Button> btns; - btns.reserve(row.size()); - for_const (const HistoryMessageReplyMarkup::Button &button, row) { - btns.push_back(Button(button)); - if (btns.size() > 16) break; - } - if (!btns.isEmpty()) { - _btns.push_back(btns); - if (_btns.size() > 512) break; - } - } + _impl.reset(markup->rows.isEmpty() ? nullptr : new ReplyKeyboard(to, MakeUnique<Style>(this, *_st))); - updateStyle(); - _height = (_btns.size() + 1) * _st->margin + _btns.size() * _st->height; - if (_maximizeSize) _height = qMax(_height, _maxOuterHeight); - if (height() != _height) { - resize(width(), _height); - } else { - resizeEvent(0); - } + updateStyle(); + _height = 2 * _st->margin + (_impl ? _impl->naturalHeight() : 0); + if (_maximizeSize) _height = qMax(_height, _maxOuterHeight); + if (height() != _height) { + resize(width(), _height); + } else { + resizeEvent(0); } return true; } @@ -2335,41 +2284,23 @@ bool BotKeyboard::updateMarkup(HistoryItem *to) { _maximizeSize = _singleUse = _forceReply = false; _wasForMsgId = FullMsgId(); clearSelection(); - _btns.clear(); + _impl.reset(); return true; } return false; } bool BotKeyboard::hasMarkup() const { - return !_btns.isEmpty(); + return !_impl.isNull(); } bool BotKeyboard::forceReply() const { return _forceReply; } -void BotKeyboard::step_selected(uint64 ms, bool timer) { - for (Animations::iterator i = _animations.begin(); i != _animations.end();) { - int index = qAbs(i.key()) - 1, row = (index / MatrixRowShift), col = index % MatrixRowShift; - float64 dt = float64(ms - i.value()) / st::botKbDuration; - if (dt >= 1) { - _btns[row][col].hover = (i.key() > 0) ? 1 : 0; - i = _animations.erase(i); - } else { - _btns[row][col].hover = (i.key() > 0) ? dt : (1 - dt); - ++i; - } - } - if (timer) update(); - if (_animations.isEmpty()) { - _a_selected.stop(); - } -} - -void BotKeyboard::resizeToWidth(int32 width, int32 maxOuterHeight) { +void BotKeyboard::resizeToWidth(int width, int maxOuterHeight) { updateStyle(width); - _height = (_btns.size() + 1) * _st->margin + _btns.size() * _st->height; + _height = 2 * _st->margin + (_impl ? _impl->naturalHeight() : 0); _maxOuterHeight = maxOuterHeight; if (_maximizeSize) _height = qMax(_height, _maxOuterHeight); @@ -2385,35 +2316,17 @@ bool BotKeyboard::singleUse() const { } void BotKeyboard::updateStyle(int32 w) { - if (w < 0) w = width(); - _st = &st::botKbButton; - for (int32 i = 0, l = _btns.size(); i != l; ++i) { - int32 j = 0, s = _btns.at(i).size(); - int32 widthLeft = w - (s * _st->margin + st::botKbScroll.width + s * 2 * _st->padding); - for (; j != s; ++j) { - Button &btn(_btns[i][j]); - if (btn.text.isEmpty()) btn.text.setText(st::botKbFont, textOneLine(btn.button.text), _textPlainOptions); - widthLeft -= qMax(btn.text.maxWidth(), 1); - if (widthLeft < 0) break; - } - if (j != s && s > 3) { - _st = &st::botKbTinyButton; - break; - } - } + if (!_impl) return; + + int implWidth = ((w < 0) ? width() : w) - _st->margin - st::botKbScroll.width; + _st = _impl->isEnoughSpace(implWidth, st::botKbButton) ? &st::botKbButton : &st::botKbTinyButton; + + _impl->setStyle(MakeUnique<Style>(this, *_st)); } void BotKeyboard::clearSelection() { - for (Animations::const_iterator i = _animations.cbegin(), e = _animations.cend(); i != e; ++i) { - int index = qAbs(i.key()) - 1, row = (index / MatrixRowShift), col = index % MatrixRowShift; - _btns[row][col].hover = 0; - } - _animations.clear(); - _a_selected.stop(); - if (_sel >= 0) { - int row = (_sel / MatrixRowShift), col = _sel % MatrixRowShift; - _btns[row][col].hover = 0; - _sel = -1; + if (_impl) { + _impl->clearSelection(); } } @@ -2422,11 +2335,9 @@ QPoint BotKeyboard::tooltipPos() const { } QString BotKeyboard::tooltipText() const { - if (_sel >= 0) { - int row = (_sel / MatrixRowShift), col = _sel % MatrixRowShift; - if (!_btns.at(row).at(col).full) { - return _btns.at(row).at(col).button.text; - } + TextLinkPtr lnk = textlnkOver(); + if (lnk && !lnk->fullDisplayed()) { + return lnk->readable(); } return QString(); } @@ -2434,47 +2345,29 @@ QString BotKeyboard::tooltipText() const { void BotKeyboard::updateSelected() { PopupTooltip::Show(1000, this); - if (_down >= 0) return; + if (textlnkDown() || !_impl) return; QPoint p(mapFromGlobal(_lastMousePos)); - int32 newSel = -1; - for (int32 i = 0, l = _btns.size(); i != l; ++i) { - for (int32 j = 0, s = _btns.at(i).size(); j != s; ++j) { - QRect r(_btns.at(i).at(j).rect); + int x = rtl() ? st::botKbScroll.width : _st->margin; - if (rtl()) r.moveLeft(width() - r.left() - r.width()); - - if (r.contains(p)) { - newSel = i * MatrixRowShift + j; - break; + TextLinkPtr lnk; + _impl->getState(lnk, p.x() - x, p.y() - _st->margin); + if (lnk != textlnkOver()) { + if (textlnkOver()) { + if (HistoryItem *item = App::hoveredLinkItem()) { + item->linkOut(textlnkOver()); + Ui::repaintHistoryItem(item); + } else { + App::main()->update();// update(_botDescRect); + _impl->linkOut(textlnkOver()); } } - if (newSel >= 0) break; - } - if (newSel != _sel) { + textlnkOver(lnk); + _impl->linkOver(lnk); PopupTooltip::Hide(); - if (newSel < 0) { - setCursor(style::cur_default); - } else if (_sel < 0) { - setCursor(style::cur_pointer); - } - bool startanim = false; - if (_sel >= 0) { - _animations.remove(_sel + 1); - if (_animations.find(-_sel - 1) == _animations.end()) { - if (_animations.isEmpty()) startanim = true; - _animations.insert(-_sel - 1, getms()); - } - } - _sel = newSel; - if (_sel >= 0) { - _animations.remove(-_sel - 1); - if (_animations.find(_sel + 1) == _animations.end()) { - if (_animations.isEmpty()) startanim = true; - _animations.insert(_sel + 1, getms()); - } - } - if (startanim && !_a_selected.animating()) _a_selected.start(); + App::hoveredLinkItem(nullptr); + setCursor(lnk ? style::cur_pointer : style::cur_default); + update(); } } @@ -3740,12 +3633,12 @@ void HistoryWidget::showHistory(const PeerId &peerId, MsgId showAtMsgId, bool re _selCount = 0; App::main()->topBar()->showSelected(0); - App::hoveredItem(0); - App::pressedItem(0); - App::hoveredLinkItem(0); - App::pressedLinkItem(0); - App::contextItem(0); - App::mousedItem(0); + App::hoveredItem(nullptr); + App::pressedItem(nullptr); + App::hoveredLinkItem(nullptr); + App::pressedLinkItem(nullptr); + App::contextItem(nullptr); + App::mousedItem(nullptr); if (_peer) { App::forgetMedia(); diff --git a/Telegram/SourceFiles/historywidget.h b/Telegram/SourceFiles/historywidget.h index 5fe436c04..94e168cb4 100644 --- a/Telegram/SourceFiles/historywidget.h +++ b/Telegram/SourceFiles/historywidget.h @@ -304,7 +304,7 @@ public: bool forceReply() const; void step_selected(uint64 ms, bool timer); - void resizeToWidth(int32 width, int32 maxOuterHeight); + void resizeToWidth(int width, int maxOuterHeight); bool maximizeSize() const; bool singleUse() const; @@ -327,30 +327,32 @@ private: void clearSelection(); FullMsgId _wasForMsgId; - int32 _height, _maxOuterHeight; - bool _maximizeSize, _singleUse, _forceReply; + int _height = 0; + int _maxOuterHeight = 0; + bool _maximizeSize = false; + bool _singleUse = false; + bool _forceReply = false; QPoint _lastMousePos; - struct Button { - Button() = default; - Button(const HistoryMessageReplyMarkup::Button &button) : button(button) { + UniquePointer<ReplyKeyboard> _impl; + + class Style : public ReplyKeyboard::Style { + public: + Style(BotKeyboard *parent, const style::botKeyboardButton &st) : ReplyKeyboard::Style(st), _parent(parent) { } - HistoryMessageReplyMarkup::Button button; - Text text = { 1 }; - QRect rect; - int cwidth = 0; - float64 hover = 0.; - bool full = true; + void startPaint(Painter &p) const override; + style::font textFont() const override; + void repaint(const HistoryItem *item) const override; + + protected: + void paintButtonBg(Painter &p, const QRect &rect, bool down, float64 howMuchOver) const override; + + private: + BotKeyboard *_parent; + }; - int32 _sel, _down; - QList<QList<Button> > _btns; - - typedef QMap<int32, uint64> Animations; - Animations _animations; - Animation _a_selected; - - const style::botKeyboardButton *_st; + const style::botKeyboardButton *_st = &st::botKbButton; }; diff --git a/Telegram/SourceFiles/mtproto/connection.cpp b/Telegram/SourceFiles/mtproto/connection.cpp index d05737382..49cbd9f48 100644 --- a/Telegram/SourceFiles/mtproto/connection.cpp +++ b/Telegram/SourceFiles/mtproto/connection.cpp @@ -62,7 +62,7 @@ bool parsePQ(const string &pqStr, string &pStr, string &qStr) { break; } } - if (p > q) swap(p, q); + if (p > q) std::swap(p, q); pStr.resize(4); uchar *pChars = (uchar*)&pStr[0]; diff --git a/Telegram/SourceFiles/types.h b/Telegram/SourceFiles/types.h index a038d848d..df9fd6d27 100644 --- a/Telegram/SourceFiles/types.h +++ b/Telegram/SourceFiles/types.h @@ -211,27 +211,69 @@ typedef double float64; using std::string; using std::exception; -using std::swap; // we copy some parts of C++11/14/17 std:: library, because on OS X 10.6+ // version we can use C++11/14/17, but we can not use its library :( namespace std_ { +template <typename T, T V> +struct integral_constant { + static constexpr T value = V; + + using value_type = T; + using type = integral_constant<T, V>; + + constexpr operator value_type() const noexcept { + return (value); + } + + constexpr value_type operator()() const noexcept { + return (value); + } +}; + +using true_type = integral_constant<bool, true>; +using false_type = integral_constant<bool, false>; + template <typename T> struct remove_reference { - typedef T type; + using type = T; }; template <typename T> struct remove_reference<T&> { - typedef T type; + using type = T; }; template <typename T> struct remove_reference<T&&> { - typedef T type; + using type = T; }; template <typename T> -inline typename remove_reference<T>::type &&move(T &&value) { +struct is_lvalue_reference : false_type { +}; +template <typename T> +struct is_lvalue_reference<T&> : true_type { +}; + +template <typename T> +struct is_rvalue_reference : false_type { +}; +template <typename T> +struct is_rvalue_reference<T&&> : true_type { +}; + +template <typename T> +inline constexpr T &&forward(typename remove_reference<T>::type &value) noexcept { + return static_cast<T&&>(value); +} +template <typename T> +inline constexpr T &&forward(typename remove_reference<T>::type &&value) noexcept { + static_assert(!is_lvalue_reference<T>::value, "bad forward call"); + return static_cast<T&&>(value); +} + +template <typename T> +inline constexpr typename remove_reference<T>::type &&move(T &&value) noexcept { return static_cast<typename remove_reference<T>::type&&>(value); } @@ -239,15 +281,12 @@ template <typename T> struct add_const { using type = const T; }; - template <typename T> using add_const_t = typename add_const<T>::type; - template <typename T> constexpr add_const_t<T> &as_const(T& t) noexcept { return t; } - template <typename T> void as_const(const T&&) = delete; @@ -688,18 +727,28 @@ public: } UniquePointer(const UniquePointer<T> &other) = delete; UniquePointer &operator=(const UniquePointer<T> &other) = delete; - UniquePointer(UniquePointer<T> &&other) : _p(getPointerAndReset(other._p)) { + UniquePointer(UniquePointer<T> &&other) : _p(other.release()) { } UniquePointer &operator=(UniquePointer<T> &&other) { std::swap(_p, other._p); return *this; } + template <typename U> + UniquePointer(UniquePointer<U> &&other) : _p(other.release()) { + } T *data() const { return _p; } + T *release() { + return getPointerAndReset(_p); + } void reset(T *p = nullptr) { *this = UniquePointer<T>(p); } + bool isNull() const { + return data() == nullptr; + } + void clear() { reset(); } @@ -707,20 +756,24 @@ public: return data(); } T &operator*() const { - t_assert(data() != nullptr); + t_assert(!isNull()); return *data(); } explicit operator bool() const { - return data() != nullptr; + return !isNull(); } ~UniquePointer() { - delete _p; + delete data(); } private: T *_p; }; +template <typename T, class... Args> +inline UniquePointer<T> MakeUnique(Args&&... args) { + return UniquePointer<T>(new T(std_::forward<Args>(args)...)); +} template <typename I> inline void destroyImplementation(I *&ptr) { @@ -959,13 +1012,13 @@ private: }; -template <typename R, typename ... Args> +template <typename R, typename... Args> class SharedCallback { public: - virtual R call(Args ... args) const = 0; + virtual R call(Args... args) const = 0; virtual ~SharedCallback() { } - typedef QSharedPointer<SharedCallback<R, Args ...>> Ptr; + typedef QSharedPointer<SharedCallback<R, Args...>> Ptr; }; template <typename R>