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>