mirror of https://github.com/procxx/kepka.git
Redesign emoji suggestions widget.
This commit is contained in:
parent
f76dc74040
commit
a12bc60ef5
|
@ -294,4 +294,7 @@ notifyFadeRight: icon {{ "fade_horizontal", notificationBg }};
|
||||||
stickerIconLeft: icon {{ "fade_horizontal-flip_horizontal", emojiPanCategories }};
|
stickerIconLeft: icon {{ "fade_horizontal-flip_horizontal", emojiPanCategories }};
|
||||||
stickerIconRight: icon {{ "fade_horizontal", emojiPanCategories }};
|
stickerIconRight: icon {{ "fade_horizontal", emojiPanCategories }};
|
||||||
|
|
||||||
|
emojiSuggestionsFadeLeft: icon {{ "fade_horizontal-flip_horizontal", boxBg }};
|
||||||
|
emojiSuggestionsFadeRight: icon {{ "fade_horizontal", boxBg }};
|
||||||
|
|
||||||
transparentPlaceholderSize: 4px;
|
transparentPlaceholderSize: 4px;
|
||||||
|
|
|
@ -227,20 +227,13 @@ gifsSearchIcon: boxFieldSearchIcon;
|
||||||
gifsSearchIconPosition: point(6px, 7px);
|
gifsSearchIconPosition: point(6px, 7px);
|
||||||
|
|
||||||
emojiSuggestionsDropdown: InnerDropdown(defaultInnerDropdown) {
|
emojiSuggestionsDropdown: InnerDropdown(defaultInnerDropdown) {
|
||||||
scroll: ScrollArea(defaultSolidScroll) {
|
scrollMargin: margins(0px, emojiColorsPadding, 0px, emojiColorsPadding);
|
||||||
deltat: 0px;
|
scrollPadding: margins(0px, 0px, 0px, 0px);
|
||||||
deltab: 0px;
|
|
||||||
round: 1px;
|
|
||||||
width: 8px;
|
|
||||||
deltax: 3px;
|
|
||||||
}
|
|
||||||
scrollMargin: margins(0px, 5px, 0px, 5px);
|
|
||||||
scrollPadding: margins(0px, 3px, 0px, 3px);
|
|
||||||
}
|
|
||||||
emojiSuggestionsMenu: Menu(defaultMenu) {
|
|
||||||
itemPadding: margins(48px, 8px, 17px, 7px);
|
|
||||||
widthMax: 512px;
|
|
||||||
}
|
}
|
||||||
|
emojiSuggestionSize: 40px;
|
||||||
|
emojiSuggestionsScrolledWidth: 240px;
|
||||||
|
emojiSuggestionsPadding: margins(emojiColorsPadding, 0px, emojiColorsPadding, 0px);
|
||||||
|
emojiSuggestionsFadeAfter: 20px;
|
||||||
|
|
||||||
mentionHeight: 40px;
|
mentionHeight: 40px;
|
||||||
mentionScroll: ScrollArea(defaultScrollArea) {
|
mentionScroll: ScrollArea(defaultScrollArea) {
|
||||||
|
|
|
@ -24,57 +24,17 @@ namespace Emoji {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr auto kShowExactDelay = crl::time(300);
|
constexpr auto kShowExactDelay = crl::time(300);
|
||||||
constexpr auto kRowLimit = 5;
|
constexpr auto kMaxNonScrolledEmoji = 7;
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
class SuggestionsWidget::Row {
|
SuggestionsWidget::SuggestionsWidget(QWidget *parent)
|
||||||
public:
|
: RpWidget(parent)
|
||||||
Row(not_null<EmojiPtr> emoji, const QString &label, const QString &replacement);
|
, _oneWidth(st::emojiSuggestionSize)
|
||||||
Row(const Row &other) = delete;
|
, _padding(st::emojiSuggestionsPadding) {
|
||||||
Row &operator=(const Row &other) = delete;
|
resize(
|
||||||
Row(Row &&other) = default;
|
_oneWidth + _padding.left() + _padding.right(),
|
||||||
Row &operator=(Row &&other) = default;
|
_oneWidth + _padding.top() + _padding.bottom());
|
||||||
~Row();
|
|
||||||
|
|
||||||
not_null<EmojiPtr> emoji() const {
|
|
||||||
return _emoji;
|
|
||||||
}
|
|
||||||
const QString &label() const {
|
|
||||||
return _label;
|
|
||||||
}
|
|
||||||
const QString &replacement() const {
|
|
||||||
return _replacement;
|
|
||||||
}
|
|
||||||
RippleAnimation *ripple() const {
|
|
||||||
return _ripple.get();
|
|
||||||
}
|
|
||||||
void setRipple(std::unique_ptr<RippleAnimation> ripple) {
|
|
||||||
_ripple = std::move(ripple);
|
|
||||||
}
|
|
||||||
void resetRipple() {
|
|
||||||
_ripple.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
not_null<EmojiPtr> _emoji;
|
|
||||||
QString _label;
|
|
||||||
QString _replacement;
|
|
||||||
std::unique_ptr<RippleAnimation> _ripple;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
SuggestionsWidget::Row::Row(not_null<EmojiPtr> emoji, const QString &label, const QString &replacement)
|
|
||||||
: _emoji(emoji)
|
|
||||||
, _label(label)
|
|
||||||
, _replacement(replacement) {
|
|
||||||
}
|
|
||||||
|
|
||||||
SuggestionsWidget::Row::~Row() = default;
|
|
||||||
|
|
||||||
SuggestionsWidget::SuggestionsWidget(QWidget *parent, const style::Menu &st) : TWidget(parent)
|
|
||||||
, _st(&st)
|
|
||||||
, _rowHeight(_st->itemPadding.top() + _st->itemFont->height + _st->itemPadding.bottom()) {
|
|
||||||
setMouseTracking(true);
|
setMouseTracking(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,20 +59,31 @@ void SuggestionsWidget::showWithQuery(const QString &query, bool force) {
|
||||||
_rows = std::move(rows);
|
_rows = std::move(rows);
|
||||||
resizeToRows();
|
resizeToRows();
|
||||||
update();
|
update();
|
||||||
if (!_rows.empty()) {
|
|
||||||
|
Ui::PostponeCall(this, [=] {
|
||||||
|
if (!_rows.empty()) {
|
||||||
|
_toggleAnimated.fire(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void SuggestionsWidget::selectFirstResult() {
|
||||||
|
if (!_rows.empty() && _selected < 0) {
|
||||||
setSelected(0);
|
setSelected(0);
|
||||||
}
|
}
|
||||||
if (!_rows.empty()) {
|
|
||||||
_toggleAnimated.fire(true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<SuggestionsWidget::Row> SuggestionsWidget::getRowsByQuery() const {
|
SuggestionsWidget::Row::Row(
|
||||||
auto result = std::vector<Row>();
|
not_null<EmojiPtr> emoji,
|
||||||
|
const QString &replacement)
|
||||||
|
: emoji(emoji)
|
||||||
|
, replacement(replacement) {
|
||||||
|
}
|
||||||
|
|
||||||
|
auto SuggestionsWidget::getRowsByQuery() const -> std::vector<Row> {
|
||||||
if (_query.isEmpty()) {
|
if (_query.isEmpty()) {
|
||||||
return result;
|
return {};
|
||||||
}
|
}
|
||||||
auto suggestions = std::vector<Row>();
|
|
||||||
const auto middle = (_query[0] == ':');
|
const auto middle = (_query[0] == ':');
|
||||||
const auto real = middle ? _query.mid(1) : _query;
|
const auto real = middle ? _query.mid(1) : _query;
|
||||||
const auto simple = [&] {
|
const auto simple = [&] {
|
||||||
|
@ -124,46 +95,35 @@ std::vector<SuggestionsWidget::Row> SuggestionsWidget::getRowsByQuery() const {
|
||||||
== _query.end();
|
== _query.end();
|
||||||
}();
|
}();
|
||||||
const auto exact = !middle || simple;
|
const auto exact = !middle || simple;
|
||||||
const auto results = Core::App().emojiKeywords().query(real, exact);
|
const auto list = Core::App().emojiKeywords().query(real, exact);
|
||||||
for (const auto &result : results) {
|
if (list.empty()) {
|
||||||
suggestions.emplace_back(
|
return {};
|
||||||
result.emoji,
|
|
||||||
result.label,
|
|
||||||
result.replacement);
|
|
||||||
}
|
}
|
||||||
if (suggestions.empty()) {
|
using Entry = ChatHelpers::EmojiKeywords::Result;
|
||||||
return result;
|
auto result = ranges::view::all(
|
||||||
}
|
list
|
||||||
auto recents = 0;
|
) | ranges::view::transform([](const Entry &result) {
|
||||||
|
return Row(result.emoji, result.replacement);
|
||||||
|
}) | ranges::to_vector;
|
||||||
|
|
||||||
|
auto lastRecent = begin(result);
|
||||||
const auto &recent = GetRecent();
|
const auto &recent = GetRecent();
|
||||||
for (const auto &item : recent) {
|
for (const auto &item : recent) {
|
||||||
const auto emoji = item.first->original()
|
const auto emoji = item.first->original()
|
||||||
? item.first->original()
|
? item.first->original()
|
||||||
: item.first;
|
: item.first;
|
||||||
const auto it = ranges::find(suggestions, emoji, [](const Row &row) {
|
const auto it = ranges::find(result, emoji, [](const Row &row) {
|
||||||
return row.emoji().get();
|
return row.emoji.get();
|
||||||
});
|
});
|
||||||
if (it == end(suggestions)) {
|
if (it > lastRecent && it != end(result)) {
|
||||||
continue;
|
std::rotate(lastRecent, it, it + 1);
|
||||||
|
++lastRecent;
|
||||||
}
|
}
|
||||||
const auto index = (it - begin(suggestions));
|
|
||||||
if (index < recents) {
|
|
||||||
continue;
|
|
||||||
} else if (index > recents) {
|
|
||||||
auto recentSuggestion = std::move(suggestions[index]);
|
|
||||||
for (auto i = index; i != recents; --i) {
|
|
||||||
suggestions[i] = std::move(suggestions[i - 1]);
|
|
||||||
}
|
|
||||||
suggestions[recents] = std::move(recentSuggestion);
|
|
||||||
}
|
|
||||||
++recents;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result.reserve(kRowLimit);
|
for (auto &item : result) {
|
||||||
auto index = 0;
|
item.emoji = [&] {
|
||||||
for (const auto &item : suggestions) {
|
const auto result = item.emoji;
|
||||||
const auto emoji = [&] {
|
|
||||||
const auto result = item.emoji();
|
|
||||||
const auto &variants = cEmojiVariants();
|
const auto &variants = cEmojiVariants();
|
||||||
const auto i = result->hasVariants()
|
const auto i = result->hasVariants()
|
||||||
? variants.constFind(result->nonColoredId())
|
? variants.constFind(result->nonColoredId())
|
||||||
|
@ -172,66 +132,129 @@ std::vector<SuggestionsWidget::Row> SuggestionsWidget::getRowsByQuery() const {
|
||||||
? result->variant(i.value())
|
? result->variant(i.value())
|
||||||
: result.get();
|
: result.get();
|
||||||
}();
|
}();
|
||||||
result.emplace_back(emoji, item.label(), item.replacement());
|
|
||||||
if (result.size() == kRowLimit) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SuggestionsWidget::resizeToRows() {
|
void SuggestionsWidget::resizeToRows() {
|
||||||
auto newWidth = 0;
|
const auto count = int(_rows.size());
|
||||||
for (auto &row : _rows) {
|
const auto scrolled = (count > kMaxNonScrolledEmoji);
|
||||||
accumulate_max(newWidth, countWidth(row));
|
const auto fullWidth = count * _oneWidth;
|
||||||
}
|
const auto newWidth = scrolled
|
||||||
newWidth = snap(newWidth, _st->widthMin, _st->widthMax);
|
? st::emojiSuggestionsScrolledWidth
|
||||||
auto newHeight = _st->skip + (_rows.size() * _rowHeight) + _st->skip;
|
: fullWidth;
|
||||||
resize(newWidth, newHeight);
|
_scrollMax = std::max(0, fullWidth - newWidth);
|
||||||
|
_scroll = std::min(_scroll, _scrollMax);
|
||||||
|
resize(_padding.left() + newWidth + _padding.right(), height());
|
||||||
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
int SuggestionsWidget::countWidth(const Row &row) {
|
bool SuggestionsWidget::eventHook(QEvent *e) {
|
||||||
auto textw = _st->itemFont->width(row.label());
|
if (e->type() == QEvent::Wheel) {
|
||||||
return _st->itemPadding.left() + textw + _st->itemPadding.right();
|
selectByMouse(QCursor::pos());
|
||||||
|
if (_selected >= 0 && _pressed < 0) {
|
||||||
|
scrollByWheelEvent(static_cast<QWheelEvent*>(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return RpWidget::eventHook(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SuggestionsWidget::scrollByWheelEvent(not_null<QWheelEvent*> e) {
|
||||||
|
const auto horizontal = (e->angleDelta().x() != 0)
|
||||||
|
|| (e->orientation() == Qt::Horizontal);
|
||||||
|
const auto vertical = (e->angleDelta().y() != 0)
|
||||||
|
|| (e->orientation() == Qt::Vertical);
|
||||||
|
const auto scroll = [&] {
|
||||||
|
if (horizontal) {
|
||||||
|
const auto delta = e->pixelDelta().x()
|
||||||
|
? e->pixelDelta().x()
|
||||||
|
: e->angleDelta().x();
|
||||||
|
return snap(_scroll - ((rtl() ? -1 : 1) * delta), 0, _scrollMax);
|
||||||
|
} else if (vertical) {
|
||||||
|
const auto delta = e->pixelDelta().y()
|
||||||
|
? e->pixelDelta().y()
|
||||||
|
: e->angleDelta().y();
|
||||||
|
return snap(_scroll - delta, 0, _scrollMax);
|
||||||
|
}
|
||||||
|
return _scroll;
|
||||||
|
}();
|
||||||
|
if (_scroll != scroll) {
|
||||||
|
_scroll = scroll;
|
||||||
|
if (!_lastMousePosition) {
|
||||||
|
_lastMousePosition = QCursor::pos();
|
||||||
|
}
|
||||||
|
selectByMouse(*_lastMousePosition);
|
||||||
|
update();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SuggestionsWidget::paintEvent(QPaintEvent *e) {
|
void SuggestionsWidget::paintEvent(QPaintEvent *e) {
|
||||||
Painter p(this);
|
Painter p(this);
|
||||||
|
|
||||||
auto ms = crl::now();
|
const auto clip = e->rect();
|
||||||
auto clip = e->rect();
|
p.fillRect(clip, st::boxBg);
|
||||||
|
|
||||||
auto topskip = QRect(0, 0, width(), _st->skip);
|
const auto shift = innerShift();
|
||||||
auto bottomskip = QRect(0, height() - _st->skip, width(), _st->skip);
|
p.translate(-shift);
|
||||||
if (clip.intersects(topskip)) p.fillRect(clip.intersected(topskip), _st->itemBg);
|
const auto paint = clip.translated(shift);
|
||||||
if (clip.intersects(bottomskip)) p.fillRect(clip.intersected(bottomskip), _st->itemBg);
|
const auto from = std::max(paint.x(), 0) / _oneWidth;
|
||||||
|
const auto till = std::min(
|
||||||
|
(paint.x() + paint.width() + _oneWidth - 1) / _oneWidth,
|
||||||
|
int(_rows.size()));
|
||||||
|
|
||||||
const auto top = _st->skip;
|
for (auto i = from; i != till; ++i) {
|
||||||
p.setFont(_st->itemFont);
|
const auto &row = _rows[i];
|
||||||
const auto from = floorclamp(clip.top() - top, _rowHeight, 0, _rows.size());
|
const auto emoji = row.emoji;
|
||||||
const auto to = ceilclamp(clip.top() + clip.height() - top, _rowHeight, 0, _rows.size());
|
|
||||||
p.translate(0, top + from * _rowHeight);
|
|
||||||
for (auto i = from; i != to; ++i) {
|
|
||||||
auto &row = _rows[i];
|
|
||||||
const auto selected = (i == _selected || i == _pressed);
|
|
||||||
p.fillRect(0, 0, width(), _rowHeight, selected ? _st->itemBgOver : _st->itemBg);
|
|
||||||
if (const auto ripple = row.ripple()) {
|
|
||||||
ripple->paint(p, 0, 0, width(), ms);
|
|
||||||
if (ripple->empty()) {
|
|
||||||
row.resetRipple();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const auto emoji = row.emoji();
|
|
||||||
const auto esize = Ui::Emoji::GetSizeLarge();
|
const auto esize = Ui::Emoji::GetSizeLarge();
|
||||||
|
const auto selected = (i == _selected || i == _pressed);
|
||||||
|
const auto x = i * _oneWidth;
|
||||||
|
const auto y = 0;
|
||||||
|
if (selected) {
|
||||||
|
App::roundRect(
|
||||||
|
p,
|
||||||
|
QRect(x, y, _oneWidth, _oneWidth),
|
||||||
|
st::emojiPanHover,
|
||||||
|
StickerHoverCorners);
|
||||||
|
}
|
||||||
Ui::Emoji::Draw(
|
Ui::Emoji::Draw(
|
||||||
p,
|
p,
|
||||||
emoji,
|
emoji,
|
||||||
esize,
|
esize,
|
||||||
(_st->itemPadding.left() - (esize / cIntRetinaFactor())) / 2,
|
x + (_oneWidth - (esize / cIntRetinaFactor())) / 2,
|
||||||
(_rowHeight - (esize / cIntRetinaFactor())) / 2);
|
y + (_oneWidth - (esize / cIntRetinaFactor())) / 2);
|
||||||
p.setPen(selected ? _st->itemFgOver : _st->itemFg);
|
}
|
||||||
p.drawTextLeft(_st->itemPadding.left(), _st->itemPadding.top(), width(), row.label());
|
paintFadings(p);
|
||||||
p.translate(0, _rowHeight);
|
}
|
||||||
|
|
||||||
|
void SuggestionsWidget::paintFadings(Painter &p) const {
|
||||||
|
const auto o_left = snap(
|
||||||
|
_scroll / float64(st::emojiSuggestionsFadeAfter),
|
||||||
|
0.,
|
||||||
|
1.);
|
||||||
|
const auto shift = innerShift();
|
||||||
|
if (o_left > 0.) {
|
||||||
|
p.setOpacity(o_left);
|
||||||
|
const auto rect = myrtlrect(
|
||||||
|
shift.x(),
|
||||||
|
0,
|
||||||
|
st::emojiSuggestionsFadeLeft.width(),
|
||||||
|
height());
|
||||||
|
st::emojiSuggestionsFadeLeft.fill(p, rect);
|
||||||
|
p.setOpacity(1.);
|
||||||
|
}
|
||||||
|
const auto o_right = snap(
|
||||||
|
(_scrollMax - _scroll) / float64(st::emojiSuggestionsFadeAfter),
|
||||||
|
0.,
|
||||||
|
1.);
|
||||||
|
if (o_right > 0.) {
|
||||||
|
p.setOpacity(o_right);
|
||||||
|
const auto rect = myrtlrect(
|
||||||
|
shift.x() + width() - st::emojiSuggestionsFadeRight.width(),
|
||||||
|
0,
|
||||||
|
st::emojiSuggestionsFadeRight.width(),
|
||||||
|
height());
|
||||||
|
st::emojiSuggestionsFadeRight.fill(p, rect);
|
||||||
|
p.setOpacity(1.);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -239,22 +262,35 @@ void SuggestionsWidget::keyPressEvent(QKeyEvent *e) {
|
||||||
handleKeyEvent(e->key());
|
handleKeyEvent(e->key());
|
||||||
}
|
}
|
||||||
|
|
||||||
void SuggestionsWidget::handleKeyEvent(int key) {
|
bool SuggestionsWidget::handleKeyEvent(int key) {
|
||||||
if (key == Qt::Key_Enter || key == Qt::Key_Return || key == Qt::Key_Tab) {
|
if (key == Qt::Key_Enter || key == Qt::Key_Return) {
|
||||||
triggerSelectedRow();
|
return triggerSelectedRow();
|
||||||
return;
|
} else if (key == Qt::Key_Tab) {
|
||||||
}
|
if (_selected < 0 || _selected >= _rows.size()) {
|
||||||
if ((key != Qt::Key_Up && key != Qt::Key_Down) || _rows.size() < 1) {
|
setSelected(0);
|
||||||
return;
|
}
|
||||||
|
return triggerSelectedRow();
|
||||||
|
} else if (_rows.empty()
|
||||||
|
|| (key != Qt::Key_Up
|
||||||
|
&& key != Qt::Key_Down
|
||||||
|
&& key != Qt::Key_Left
|
||||||
|
&& key != Qt::Key_Right)) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto delta = (key == Qt::Key_Down ? 1 : -1), start = _selected;
|
const auto delta = (key == Qt::Key_Down || key == Qt::Key_Right)
|
||||||
|
? 1
|
||||||
|
: -1;
|
||||||
|
if (delta < 0 && _selected < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto start = _selected;
|
||||||
if (start < 0 || start >= _rows.size()) {
|
if (start < 0 || start >= _rows.size()) {
|
||||||
start = (delta > 0) ? (_rows.size() - 1) : 0;
|
start = (delta > 0) ? (_rows.size() - 1) : 0;
|
||||||
}
|
}
|
||||||
auto newSelected = start + delta;
|
auto newSelected = start + delta;
|
||||||
if (newSelected < 0) {
|
if (newSelected < 0) {
|
||||||
newSelected += _rows.size();
|
newSelected = -1;
|
||||||
} else if (newSelected >= _rows.size()) {
|
} else if (newSelected >= _rows.size()) {
|
||||||
newSelected -= _rows.size();
|
newSelected -= _rows.size();
|
||||||
}
|
}
|
||||||
|
@ -262,6 +298,7 @@ void SuggestionsWidget::handleKeyEvent(int key) {
|
||||||
_mouseSelection = false;
|
_mouseSelection = false;
|
||||||
_lastMousePosition = std::nullopt;
|
_lastMousePosition = std::nullopt;
|
||||||
setSelected(newSelected);
|
setSelected(newSelected);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SuggestionsWidget::setSelected(int selected) {
|
void SuggestionsWidget::setSelected(int selected) {
|
||||||
|
@ -296,16 +333,13 @@ void SuggestionsWidget::clearSelection() {
|
||||||
setSelected(-1);
|
setSelected(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
int SuggestionsWidget::itemTop(int index) {
|
|
||||||
if (index > _rows.size()) {
|
|
||||||
index = _rows.size();
|
|
||||||
}
|
|
||||||
return _st->skip + (_rowHeight * index);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SuggestionsWidget::updateItem(int index) {
|
void SuggestionsWidget::updateItem(int index) {
|
||||||
if (index >= 0 && index < _rows.size()) {
|
if (index >= 0 && index < _rows.size()) {
|
||||||
update(0, itemTop(index), width(), _rowHeight);
|
update(
|
||||||
|
_padding.left() + index * _oneWidth - _scroll,
|
||||||
|
_padding.top(),
|
||||||
|
_oneWidth,
|
||||||
|
_oneWidth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -313,11 +347,21 @@ void SuggestionsWidget::updateSelectedItem() {
|
||||||
updateItem(_selected);
|
updateItem(_selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QRect SuggestionsWidget::inner() const {
|
||||||
|
return QRect(0, 0, _rows.size() * _oneWidth, _oneWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
QPoint SuggestionsWidget::innerShift() const {
|
||||||
|
return QPoint(_scroll - _padding.left(), -_padding.top());
|
||||||
|
}
|
||||||
|
|
||||||
|
QPoint SuggestionsWidget::mapToInner(QPoint globalPosition) const {
|
||||||
|
return mapFromGlobal(globalPosition) + innerShift();
|
||||||
|
}
|
||||||
|
|
||||||
void SuggestionsWidget::mouseMoveEvent(QMouseEvent *e) {
|
void SuggestionsWidget::mouseMoveEvent(QMouseEvent *e) {
|
||||||
auto inner = rect().marginsRemoved(QMargins(0, _st->skip, 0, _st->skip));
|
const auto globalPosition = e->globalPos();
|
||||||
auto localPosition = e->pos();
|
if (inner().contains(mapToInner(globalPosition))) {
|
||||||
if (inner.contains(localPosition)) {
|
|
||||||
const auto globalPosition = e->globalPos();
|
|
||||||
if (!_lastMousePosition) {
|
if (!_lastMousePosition) {
|
||||||
_lastMousePosition = globalPosition;
|
_lastMousePosition = globalPosition;
|
||||||
return;
|
return;
|
||||||
|
@ -334,8 +378,8 @@ void SuggestionsWidget::mouseMoveEvent(QMouseEvent *e) {
|
||||||
void SuggestionsWidget::selectByMouse(QPoint globalPosition) {
|
void SuggestionsWidget::selectByMouse(QPoint globalPosition) {
|
||||||
_mouseSelection = true;
|
_mouseSelection = true;
|
||||||
_lastMousePosition = globalPosition;
|
_lastMousePosition = globalPosition;
|
||||||
auto p = mapFromGlobal(globalPosition) - QPoint(0, _st->skip);
|
const auto p = mapToInner(globalPosition);
|
||||||
auto selected = (p.y() >= 0) ? (p.y() / _rowHeight) : -1;
|
const auto selected = (p.x() >= 0) ? (p.x() / _oneWidth) : -1;
|
||||||
setSelected((selected >= 0 && selected < _rows.size()) ? selected : -1);
|
setSelected((selected >= 0 && selected < _rows.size()) ? selected : -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -343,13 +387,6 @@ void SuggestionsWidget::mousePressEvent(QMouseEvent *e) {
|
||||||
selectByMouse(e->globalPos());
|
selectByMouse(e->globalPos());
|
||||||
if (_selected >= 0 && _selected < _rows.size()) {
|
if (_selected >= 0 && _selected < _rows.size()) {
|
||||||
setPressed(_selected);
|
setPressed(_selected);
|
||||||
if (!_rows[_pressed].ripple()) {
|
|
||||||
auto mask = RippleAnimation::rectMask(QSize(width(), _rowHeight));
|
|
||||||
_rows[_pressed].setRipple(std::make_unique<RippleAnimation>(_st->ripple, std::move(mask), [this, selected = _pressed] {
|
|
||||||
updateItem(selected);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
_rows[_pressed].ripple()->add(mapFromGlobal(QCursor::pos()) - QPoint(0, itemTop(_pressed)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -357,28 +394,26 @@ void SuggestionsWidget::mouseReleaseEvent(QMouseEvent *e) {
|
||||||
if (_pressed >= 0 && _pressed < _rows.size()) {
|
if (_pressed >= 0 && _pressed < _rows.size()) {
|
||||||
auto pressed = _pressed;
|
auto pressed = _pressed;
|
||||||
setPressed(-1);
|
setPressed(-1);
|
||||||
if (_rows[pressed].ripple()) {
|
|
||||||
_rows[pressed].ripple()->lastStop();
|
|
||||||
}
|
|
||||||
if (pressed == _selected) {
|
if (pressed == _selected) {
|
||||||
triggerRow(_rows[_selected]);
|
triggerRow(_rows[_selected]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SuggestionsWidget::triggerSelectedRow() {
|
bool SuggestionsWidget::triggerSelectedRow() const {
|
||||||
if (_selected >= 0 && _selected < _rows.size()) {
|
if (_selected >= 0 && _selected < _rows.size()) {
|
||||||
triggerRow(_rows[_selected]);
|
triggerRow(_rows[_selected]);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SuggestionsWidget::triggerRow(const Row &row) {
|
void SuggestionsWidget::triggerRow(const Row &row) const {
|
||||||
_triggered.fire(row.emoji()->text());
|
_triggered.fire(row.emoji->text());
|
||||||
}
|
}
|
||||||
|
|
||||||
void SuggestionsWidget::enterEventHook(QEvent *e) {
|
void SuggestionsWidget::enterEventHook(QEvent *e) {
|
||||||
auto mouse = QCursor::pos();
|
if (!inner().contains(mapToInner(QCursor::pos()))) {
|
||||||
if (!rect().marginsRemoved(QMargins(0, _st->skip, 0, _st->skip)).contains(mapFromGlobal(mouse))) {
|
|
||||||
clearMouseSelection();
|
clearMouseSelection();
|
||||||
}
|
}
|
||||||
return TWidget::enterEventHook(e);
|
return TWidget::enterEventHook(e);
|
||||||
|
@ -399,9 +434,7 @@ SuggestionsController::SuggestionsController(
|
||||||
st::emojiSuggestionsDropdown);
|
st::emojiSuggestionsDropdown);
|
||||||
_container->setAutoHiding(false);
|
_container->setAutoHiding(false);
|
||||||
_suggestions = _container->setOwnedWidget(
|
_suggestions = _container->setOwnedWidget(
|
||||||
object_ptr<Ui::Emoji::SuggestionsWidget>(
|
object_ptr<Ui::Emoji::SuggestionsWidget>(_container));
|
||||||
_container,
|
|
||||||
st::emojiSuggestionsMenu));
|
|
||||||
|
|
||||||
setReplaceCallback(nullptr);
|
setReplaceCallback(nullptr);
|
||||||
|
|
||||||
|
@ -487,10 +520,13 @@ void SuggestionsController::handleTextChange() {
|
||||||
const auto query = getEmojiQuery();
|
const auto query = getEmojiQuery();
|
||||||
if (query.isEmpty() || _textChangeAfterKeyPress) {
|
if (query.isEmpty() || _textChangeAfterKeyPress) {
|
||||||
const auto exact = (!query.isEmpty() && query[0] != ':');
|
const auto exact = (!query.isEmpty() && query[0] != ':');
|
||||||
if (exact && (_container->isHidden() || _container->isHiding())) {
|
if (exact) {
|
||||||
_showExactTimer.callOnce(kShowExactDelay);
|
const auto hidden = _container->isHidden()
|
||||||
|
|| _container->isHiding();
|
||||||
|
_showExactTimer.callOnce(hidden ? kShowExactDelay : 0);
|
||||||
} else {
|
} else {
|
||||||
showWithQuery(query);
|
showWithQuery(query);
|
||||||
|
_suggestions->selectFirstResult();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -563,6 +599,9 @@ QString SuggestionsController::getEmojiQuery() {
|
||||||
cursor.movePosition(QTextCursor::End);
|
cursor.movePosition(QTextCursor::End);
|
||||||
return cursor.position();
|
return cursor.position();
|
||||||
}();
|
}();
|
||||||
|
const auto is = [&](QLatin1String string) {
|
||||||
|
return (text.compare(string, Qt::CaseInsensitive) == 0);
|
||||||
|
};
|
||||||
if (!length
|
if (!length
|
||||||
|| text[0].isSpace()
|
|| text[0].isSpace()
|
||||||
|| (length > modernLimit)
|
|| (length > modernLimit)
|
||||||
|
@ -570,7 +609,7 @@ QString SuggestionsController::getEmojiQuery() {
|
||||||
|| (position != end)) {
|
|| (position != end)) {
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
return text.mid(0, length);
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SuggestionsController::replaceCurrent(const QString &replacement) {
|
void SuggestionsController::replaceCurrent(const QString &replacement) {
|
||||||
|
@ -620,7 +659,9 @@ void SuggestionsController::updateGeometry() {
|
||||||
auto boundingRect = _container->parentWidget()->rect();
|
auto boundingRect = _container->parentWidget()->rect();
|
||||||
auto origin = rtl() ? PanelAnimation::Origin::BottomRight : PanelAnimation::Origin::BottomLeft;
|
auto origin = rtl() ? PanelAnimation::Origin::BottomRight : PanelAnimation::Origin::BottomLeft;
|
||||||
auto point = rtl() ? (aroundRect.topLeft() + QPoint(aroundRect.width(), 0)) : aroundRect.topLeft();
|
auto point = rtl() ? (aroundRect.topLeft() + QPoint(aroundRect.width(), 0)) : aroundRect.topLeft();
|
||||||
point -= rtl() ? QPoint(_container->width() - st::emojiSuggestionsDropdown.padding.right(), _container->height()) : QPoint(st::emojiSuggestionsDropdown.padding.left(), _container->height());
|
const auto padding = st::emojiSuggestionsDropdown.padding;
|
||||||
|
const auto shift = std::min(_container->width() - padding.left() - padding.right(), st::emojiSuggestionSize) / 2;
|
||||||
|
point -= rtl() ? QPoint(_container->width() - padding.right() - shift, _container->height()) : QPoint(padding.left() + shift, _container->height());
|
||||||
if (rtl()) {
|
if (rtl()) {
|
||||||
if (point.x() < boundingRect.x()) {
|
if (point.x() < boundingRect.x()) {
|
||||||
point.setX(boundingRect.x());
|
point.setX(boundingRect.x());
|
||||||
|
@ -679,9 +720,10 @@ bool SuggestionsController::fieldFilter(not_null<QEvent*> event) {
|
||||||
case Qt::Key_Tab:
|
case Qt::Key_Tab:
|
||||||
case Qt::Key_Up:
|
case Qt::Key_Up:
|
||||||
case Qt::Key_Down:
|
case Qt::Key_Down:
|
||||||
|
case Qt::Key_Left:
|
||||||
|
case Qt::Key_Right:
|
||||||
if (_shown && !_forceHidden) {
|
if (_shown && !_forceHidden) {
|
||||||
_suggestions->handleKeyEvent(key);
|
return _suggestions->handleKeyEvent(key);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "ui/effects/panel_animation.h"
|
#include "ui/effects/panel_animation.h"
|
||||||
|
#include "ui/rp_widget.h"
|
||||||
#include "base/unique_qptr.h"
|
#include "base/unique_qptr.h"
|
||||||
#include "base/timer.h"
|
#include "base/timer.h"
|
||||||
|
|
||||||
|
@ -18,17 +19,26 @@ class InputField;
|
||||||
|
|
||||||
namespace Emoji {
|
namespace Emoji {
|
||||||
|
|
||||||
class SuggestionsWidget : public TWidget {
|
class SuggestionsWidget final : public Ui::RpWidget {
|
||||||
public:
|
public:
|
||||||
SuggestionsWidget(QWidget *parent, const style::Menu &st);
|
SuggestionsWidget(QWidget *parent);
|
||||||
|
|
||||||
void showWithQuery(const QString &query, bool force = false);
|
void showWithQuery(const QString &query, bool force = false);
|
||||||
void handleKeyEvent(int key);
|
void selectFirstResult();
|
||||||
|
bool handleKeyEvent(int key);
|
||||||
|
|
||||||
rpl::producer<bool> toggleAnimated() const;
|
rpl::producer<bool> toggleAnimated() const;
|
||||||
rpl::producer<QString> triggered() const;
|
rpl::producer<QString> triggered() const;
|
||||||
|
|
||||||
protected:
|
private:
|
||||||
|
struct Row {
|
||||||
|
Row(not_null<EmojiPtr> emoji, const QString &replacement);
|
||||||
|
|
||||||
|
not_null<EmojiPtr> emoji;
|
||||||
|
QString replacement;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool eventHook(QEvent *e) override;
|
||||||
void paintEvent(QPaintEvent *e) override;
|
void paintEvent(QPaintEvent *e) override;
|
||||||
void keyPressEvent(QKeyEvent *e) override;
|
void keyPressEvent(QKeyEvent *e) override;
|
||||||
void mouseMoveEvent(QMouseEvent *e) override;
|
void mouseMoveEvent(QMouseEvent *e) override;
|
||||||
|
@ -37,34 +47,37 @@ protected:
|
||||||
void enterEventHook(QEvent *e) override;
|
void enterEventHook(QEvent *e) override;
|
||||||
void leaveEventHook(QEvent *e) override;
|
void leaveEventHook(QEvent *e) override;
|
||||||
|
|
||||||
private:
|
void scrollByWheelEvent(not_null<QWheelEvent*> e);
|
||||||
class Row;
|
void paintFadings(Painter &p) const;
|
||||||
|
|
||||||
std::vector<Row> getRowsByQuery() const;
|
std::vector<Row> getRowsByQuery() const;
|
||||||
void resizeToRows();
|
void resizeToRows();
|
||||||
int countWidth(const Row &row);
|
|
||||||
void setSelected(int selected);
|
void setSelected(int selected);
|
||||||
void setPressed(int pressed);
|
void setPressed(int pressed);
|
||||||
void clearMouseSelection();
|
void clearMouseSelection();
|
||||||
void clearSelection();
|
void clearSelection();
|
||||||
void updateSelectedItem();
|
void updateSelectedItem();
|
||||||
int itemTop(int index);
|
|
||||||
void updateItem(int index);
|
void updateItem(int index);
|
||||||
|
[[nodiscard]] QRect inner() const;
|
||||||
|
[[nodiscard]] QPoint innerShift() const;
|
||||||
|
[[nodiscard]] QPoint mapToInner(QPoint globalPosition) const;
|
||||||
void selectByMouse(QPoint globalPosition);
|
void selectByMouse(QPoint globalPosition);
|
||||||
void triggerSelectedRow();
|
bool triggerSelectedRow() const;
|
||||||
void triggerRow(const Row &row);
|
void triggerRow(const Row &row) const;
|
||||||
|
|
||||||
not_null<const style::Menu*> _st;
|
|
||||||
|
|
||||||
QString _query;
|
QString _query;
|
||||||
std::vector<Row> _rows;
|
std::vector<Row> _rows;
|
||||||
|
|
||||||
int _rowHeight = 0;
|
|
||||||
std::optional<QPoint> _lastMousePosition;
|
std::optional<QPoint> _lastMousePosition;
|
||||||
bool _mouseSelection = false;
|
bool _mouseSelection = false;
|
||||||
int _selected = -1;
|
int _selected = -1;
|
||||||
int _pressed = -1;
|
int _pressed = -1;
|
||||||
|
|
||||||
|
int _scroll = 0;
|
||||||
|
int _scrollMax = 0;
|
||||||
|
int _oneWidth = 0;
|
||||||
|
QMargins _padding;
|
||||||
|
|
||||||
rpl::event_stream<bool> _toggleAnimated;
|
rpl::event_stream<bool> _toggleAnimated;
|
||||||
rpl::event_stream<QString> _triggered;
|
rpl::event_stream<QString> _triggered;
|
||||||
|
|
||||||
|
@ -90,7 +103,7 @@ private:
|
||||||
void handleCursorPositionChange();
|
void handleCursorPositionChange();
|
||||||
void handleTextChange();
|
void handleTextChange();
|
||||||
void showWithQuery(const QString &query);
|
void showWithQuery(const QString &query);
|
||||||
QString getEmojiQuery();
|
[[nodiscard]] QString getEmojiQuery();
|
||||||
void suggestionsUpdated(bool visible);
|
void suggestionsUpdated(bool visible);
|
||||||
void updateGeometry();
|
void updateGeometry();
|
||||||
void updateForceHidden();
|
void updateForceHidden();
|
||||||
|
|
Loading…
Reference in New Issue