From 2c6f74f923feed04d1e7874d71af8965e64db4c3 Mon Sep 17 00:00:00 2001 From: John Preston 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(s)) { + if (auto *markup = item->Get()) { + _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(); + 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(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(); @@ -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 { }; 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