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.
This commit is contained in:
John Preston 2016-03-28 20:15:17 +03:00
parent daa5016c23
commit 2c6f74f923
6 changed files with 509 additions and 254 deletions

View File

@ -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()) {

View File

@ -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;

View File

@ -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();

View File

@ -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;
};

View File

@ -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];

View File

@ -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>