2
0
mirror of https://github.com/kotatogram/kotatogram-desktop synced 2025-08-31 06:35:14 +00:00

Add FieldAutocomplete to ComposeControls.

This commit is contained in:
John Preston
2020-11-10 17:10:08 +03:00
parent 5d2ffae215
commit ac02e2be9e
9 changed files with 444 additions and 199 deletions

View File

@@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/stickers/data_stickers.h"
#include "chat_helpers/send_context_menu.h" // SendMenu::FillSendMenu
#include "chat_helpers/stickers_lottie.h"
#include "chat_helpers/message_field.h" // PrepareMentionTag.
#include "mainwindow.h"
#include "apiwrap.h"
#include "main/main_session.h"
@@ -27,6 +28,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lottie/lottie_single_player.h"
#include "ui/widgets/popup_menu.h"
#include "ui/widgets/scroll_area.h"
#include "ui/widgets/input_fields.h"
#include "ui/image/image.h"
#include "ui/ui_utility.h"
#include "ui/cached_round_corners.h"
@@ -39,15 +41,105 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtWidgets/QApplication>
class FieldAutocomplete::Inner final
: public Ui::RpWidget
, private base::Subscriber {
public:
struct ScrollTo {
int top;
int bottom;
};
Inner(
not_null<Window::SessionController*> controller,
not_null<FieldAutocomplete*> parent,
not_null<MentionRows*> mrows,
not_null<HashtagRows*> hrows,
not_null<BotCommandRows*> brows,
not_null<StickerRows*> srows);
void clearSel(bool hidden = false);
bool moveSel(int key);
bool chooseSelected(FieldAutocomplete::ChooseMethod method) const;
bool chooseAtIndex(
FieldAutocomplete::ChooseMethod method,
int index,
Api::SendOptions options = Api::SendOptions()) const;
void setRecentInlineBotsInRows(int32 bots);
void rowsUpdated();
rpl::producer<FieldAutocomplete::MentionChosen> mentionChosen() const;
rpl::producer<FieldAutocomplete::HashtagChosen> hashtagChosen() const;
rpl::producer<FieldAutocomplete::BotCommandChosen>
botCommandChosen() const;
rpl::producer<FieldAutocomplete::StickerChosen> stickerChosen() const;
rpl::producer<ScrollTo> scrollToRequested() const;
void onParentGeometryChanged();
private:
void paintEvent(QPaintEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
void enterEventHook(QEvent *e) override;
void leaveEventHook(QEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
void contextMenuEvent(QContextMenuEvent *e) override;
void updateSelectedRow();
void setSel(int sel, bool scroll = false);
void showPreview();
void selectByMouse(QPoint global);
QSize stickerBoundingBox() const;
void setupLottie(StickerSuggestion &suggestion);
void repaintSticker(not_null<DocumentData*> document);
std::shared_ptr<Lottie::FrameRenderer> getLottieRenderer();
const not_null<Window::SessionController*> _controller;
const not_null<FieldAutocomplete*> _parent;
const not_null<MentionRows*> _mrows;
const not_null<HashtagRows*> _hrows;
const not_null<BotCommandRows*> _brows;
const not_null<StickerRows*> _srows;
rpl::lifetime _stickersLifetime;
std::weak_ptr<Lottie::FrameRenderer> _lottieRenderer;
base::unique_qptr<Ui::PopupMenu> _menu;
int _stickersPerRow = 1;
int _recentInlineBotsInRows = 0;
int _sel = -1;
int _down = -1;
std::optional<QPoint> _lastMousePosition;
bool _mouseSelection = false;
bool _overDelete = false;
bool _previewShown = false;
rpl::event_stream<FieldAutocomplete::MentionChosen> _mentionChosen;
rpl::event_stream<FieldAutocomplete::HashtagChosen> _hashtagChosen;
rpl::event_stream<FieldAutocomplete::BotCommandChosen> _botCommandChosen;
rpl::event_stream<FieldAutocomplete::StickerChosen> _stickerChosen;
rpl::event_stream<ScrollTo> _scrollToRequested;
base::Timer _previewTimer;
};
FieldAutocomplete::FieldAutocomplete(
QWidget *parent,
not_null<Window::SessionController*> controller)
: RpWidget(parent)
, _controller(controller)
, _scroll(this, st::mentionScroll) {
_scroll->setGeometry(rect());
hide();
using Inner = internal::FieldAutocompleteInner;
_scroll->setGeometry(rect());
_inner = _scroll->setOwnedWidget(
object_ptr<Inner>(
@@ -76,6 +168,10 @@ FieldAutocomplete::FieldAutocomplete(
&Inner::onParentGeometryChanged);
}
not_null<Window::SessionController*> FieldAutocomplete::controller() const {
return _controller;
}
auto FieldAutocomplete::mentionChosen() const
-> rpl::producer<FieldAutocomplete::MentionChosen> {
return _inner->mentionChosen();
@@ -125,9 +221,9 @@ void FieldAutocomplete::showFiltered(
if (query.isEmpty()) {
_type = Type::Mentions;
rowsUpdated(
internal::MentionRows(),
internal::HashtagRows(),
internal::BotCommandRows(),
MentionRows(),
HashtagRows(),
BotCommandRows(),
base::take(_srows),
false);
return;
@@ -171,7 +267,7 @@ void FieldAutocomplete::showStickers(EmojiPtr emoji) {
base::take(_mrows),
base::take(_hrows),
base::take(_brows),
internal::StickerRows(),
StickerRows(),
false);
return;
}
@@ -203,7 +299,7 @@ inline int indexOfInFirstN(const T &v, const U &elem, int last) {
}
}
internal::StickerRows FieldAutocomplete::getStickerSuggestions() {
FieldAutocomplete::StickerRows FieldAutocomplete::getStickerSuggestions() {
const auto list = _controller->session().data().stickers().getListByEmoji(
_emoji,
_stickersSeed
@@ -211,7 +307,7 @@ internal::StickerRows FieldAutocomplete::getStickerSuggestions() {
auto result = ranges::view::all(
list
) | ranges::view::transform([](not_null<DocumentData*> sticker) {
return internal::StickerSuggestion{
return StickerSuggestion{
sticker,
sticker->createMediaView()
};
@@ -223,7 +319,7 @@ internal::StickerRows FieldAutocomplete::getStickerSuggestions() {
const auto i = ranges::find(
result,
suggestion.document,
&internal::StickerSuggestion::document);
&StickerSuggestion::document);
if (i != end(result)) {
i->animated = std::move(suggestion.animated);
}
@@ -233,10 +329,10 @@ internal::StickerRows FieldAutocomplete::getStickerSuggestions() {
void FieldAutocomplete::updateFiltered(bool resetScroll) {
int32 now = base::unixtime::now(), recentInlineBots = 0;
internal::MentionRows mrows;
internal::HashtagRows hrows;
internal::BotCommandRows brows;
internal::StickerRows srows;
MentionRows mrows;
HashtagRows hrows;
BotCommandRows brows;
StickerRows srows;
if (_emoji) {
srows = getStickerSuggestions();
} else if (_type == Type::Mentions) {
@@ -435,10 +531,10 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
}
void FieldAutocomplete::rowsUpdated(
internal::MentionRows &&mrows,
internal::HashtagRows &&hrows,
internal::BotCommandRows &&brows,
internal::StickerRows &&srows,
MentionRows &&mrows,
HashtagRows &&hrows,
BotCommandRows &&brows,
StickerRows &&srows,
bool resetScroll) {
if (mrows.empty() && hrows.empty() && brows.empty() && srows.empty()) {
if (!isHidden()) {
@@ -620,9 +716,7 @@ bool FieldAutocomplete::eventFilter(QObject *obj, QEvent *e) {
return QWidget::eventFilter(obj, e);
}
namespace internal {
FieldAutocompleteInner::FieldAutocompleteInner(
FieldAutocomplete::Inner::Inner(
not_null<Window::SessionController*> controller,
not_null<FieldAutocomplete*> parent,
not_null<MentionRows*> mrows,
@@ -642,7 +736,7 @@ FieldAutocompleteInner::FieldAutocompleteInner(
}, lifetime());
}
void FieldAutocompleteInner::paintEvent(QPaintEvent *e) {
void FieldAutocomplete::Inner::paintEvent(QPaintEvent *e) {
Painter p(this);
QRect r(e->rect());
@@ -841,11 +935,11 @@ void FieldAutocompleteInner::paintEvent(QPaintEvent *e) {
p.fillRect(Adaptive::OneColumn() ? 0 : st::lineWidth, _parent->innerTop(), width() - (Adaptive::OneColumn() ? 0 : st::lineWidth), st::lineWidth, st::shadowFg);
}
void FieldAutocompleteInner::resizeEvent(QResizeEvent *e) {
void FieldAutocomplete::Inner::resizeEvent(QResizeEvent *e) {
_stickersPerRow = qMax(1, int32(width() - 2 * st::stickerPanPadding) / int32(st::stickerPanSize.width()));
}
void FieldAutocompleteInner::mouseMoveEvent(QMouseEvent *e) {
void FieldAutocomplete::Inner::mouseMoveEvent(QMouseEvent *e) {
const auto globalPosition = e->globalPos();
if (!_lastMousePosition) {
_lastMousePosition = globalPosition;
@@ -857,7 +951,7 @@ void FieldAutocompleteInner::mouseMoveEvent(QMouseEvent *e) {
selectByMouse(globalPosition);
}
void FieldAutocompleteInner::clearSel(bool hidden) {
void FieldAutocomplete::Inner::clearSel(bool hidden) {
_overDelete = false;
_mouseSelection = false;
_lastMousePosition = std::nullopt;
@@ -868,7 +962,7 @@ void FieldAutocompleteInner::clearSel(bool hidden) {
}
}
bool FieldAutocompleteInner::moveSel(int key) {
bool FieldAutocomplete::Inner::moveSel(int key) {
_mouseSelection = false;
_lastMousePosition = std::nullopt;
@@ -903,12 +997,12 @@ bool FieldAutocompleteInner::moveSel(int key) {
return true;
}
bool FieldAutocompleteInner::chooseSelected(
bool FieldAutocomplete::Inner::chooseSelected(
FieldAutocomplete::ChooseMethod method) const {
return chooseAtIndex(method, _sel);
}
bool FieldAutocompleteInner::chooseAtIndex(
bool FieldAutocomplete::Inner::chooseAtIndex(
FieldAutocomplete::ChooseMethod method,
int index,
Api::SendOptions options) const {
@@ -955,11 +1049,11 @@ bool FieldAutocompleteInner::chooseAtIndex(
return false;
}
void FieldAutocompleteInner::setRecentInlineBotsInRows(int32 bots) {
void FieldAutocomplete::Inner::setRecentInlineBotsInRows(int32 bots) {
_recentInlineBotsInRows = bots;
}
void FieldAutocompleteInner::mousePressEvent(QMouseEvent *e) {
void FieldAutocomplete::Inner::mousePressEvent(QMouseEvent *e) {
selectByMouse(e->globalPos());
if (e->button() == Qt::LeftButton) {
if (_overDelete && _sel >= 0 && _sel < (_mrows->empty() ? _hrows->size() : _recentInlineBotsInRows)) {
@@ -999,7 +1093,7 @@ void FieldAutocompleteInner::mousePressEvent(QMouseEvent *e) {
}
}
void FieldAutocompleteInner::mouseReleaseEvent(QMouseEvent *e) {
void FieldAutocomplete::Inner::mouseReleaseEvent(QMouseEvent *e) {
_previewTimer.cancel();
int32 pressed = _down;
@@ -1017,7 +1111,7 @@ void FieldAutocompleteInner::mouseReleaseEvent(QMouseEvent *e) {
chooseSelected(FieldAutocomplete::ChooseMethod::ByClick);
}
void FieldAutocompleteInner::contextMenuEvent(QContextMenuEvent *e) {
void FieldAutocomplete::Inner::contextMenuEvent(QContextMenuEvent *e) {
if (_sel < 0 || _srows->empty() || _down >= 0) {
return;
}
@@ -1040,11 +1134,11 @@ void FieldAutocompleteInner::contextMenuEvent(QContextMenuEvent *e) {
}
}
void FieldAutocompleteInner::enterEventHook(QEvent *e) {
void FieldAutocomplete::Inner::enterEventHook(QEvent *e) {
setMouseTracking(true);
}
void FieldAutocompleteInner::leaveEventHook(QEvent *e) {
void FieldAutocomplete::Inner::leaveEventHook(QEvent *e) {
setMouseTracking(false);
if (_mouseSelection) {
setSel(-1);
@@ -1053,7 +1147,7 @@ void FieldAutocompleteInner::leaveEventHook(QEvent *e) {
}
}
void FieldAutocompleteInner::updateSelectedRow() {
void FieldAutocomplete::Inner::updateSelectedRow() {
if (_sel >= 0) {
if (_srows->empty()) {
update(0, _sel * st::mentionHeight, width(), st::mentionHeight);
@@ -1064,7 +1158,7 @@ void FieldAutocompleteInner::updateSelectedRow() {
}
}
void FieldAutocompleteInner::setSel(int sel, bool scroll) {
void FieldAutocomplete::Inner::setSel(int sel, bool scroll) {
updateSelectedRow();
_sel = sel;
updateSelectedRow();
@@ -1084,13 +1178,13 @@ void FieldAutocompleteInner::setSel(int sel, bool scroll) {
}
}
void FieldAutocompleteInner::rowsUpdated() {
void FieldAutocomplete::Inner::rowsUpdated() {
if (_srows->empty()) {
_stickersLifetime.destroy();
}
}
auto FieldAutocompleteInner::getLottieRenderer()
auto FieldAutocomplete::Inner::getLottieRenderer()
-> std::shared_ptr<Lottie::FrameRenderer> {
if (auto result = _lottieRenderer.lock()) {
return result;
@@ -1100,7 +1194,7 @@ auto FieldAutocompleteInner::getLottieRenderer()
return result;
}
void FieldAutocompleteInner::setupLottie(StickerSuggestion &suggestion) {
void FieldAutocomplete::Inner::setupLottie(StickerSuggestion &suggestion) {
const auto document = suggestion.document;
suggestion.animated = ChatHelpers::LottiePlayerFromDocument(
suggestion.documentMedia.get(),
@@ -1115,13 +1209,13 @@ void FieldAutocompleteInner::setupLottie(StickerSuggestion &suggestion) {
}, _stickersLifetime);
}
QSize FieldAutocompleteInner::stickerBoundingBox() const {
QSize FieldAutocomplete::Inner::stickerBoundingBox() const {
return QSize(
st::stickerPanSize.width() - st::buttonRadius * 2,
st::stickerPanSize.height() - st::buttonRadius * 2);
}
void FieldAutocompleteInner::repaintSticker(
void FieldAutocomplete::Inner::repaintSticker(
not_null<DocumentData*> document) {
const auto i = ranges::find(
*_srows,
@@ -1140,7 +1234,7 @@ void FieldAutocompleteInner::repaintSticker(
st::stickerPanSize.height());
}
void FieldAutocompleteInner::selectByMouse(QPoint globalPosition) {
void FieldAutocomplete::Inner::selectByMouse(QPoint globalPosition) {
_mouseSelection = true;
_lastMousePosition = globalPosition;
const auto mouse = mapFromGlobal(globalPosition);
@@ -1186,7 +1280,7 @@ void FieldAutocompleteInner::selectByMouse(QPoint globalPosition) {
}
}
void FieldAutocompleteInner::onParentGeometryChanged() {
void FieldAutocomplete::Inner::onParentGeometryChanged() {
const auto globalPosition = QCursor::pos();
if (rect().contains(mapFromGlobal(globalPosition))) {
setMouseTracking(true);
@@ -1196,7 +1290,7 @@ void FieldAutocompleteInner::onParentGeometryChanged() {
}
}
void FieldAutocompleteInner::showPreview() {
void FieldAutocomplete::Inner::showPreview() {
if (_down >= 0 && _down < _srows->size()) {
if (const auto w = App::wnd()) {
w->showMediaPreview(
@@ -1207,29 +1301,27 @@ void FieldAutocompleteInner::showPreview() {
}
}
auto FieldAutocompleteInner::mentionChosen() const
auto FieldAutocomplete::Inner::mentionChosen() const
-> rpl::producer<FieldAutocomplete::MentionChosen> {
return _mentionChosen.events();
}
auto FieldAutocompleteInner::hashtagChosen() const
auto FieldAutocomplete::Inner::hashtagChosen() const
-> rpl::producer<FieldAutocomplete::HashtagChosen> {
return _hashtagChosen.events();
}
auto FieldAutocompleteInner::botCommandChosen() const
auto FieldAutocomplete::Inner::botCommandChosen() const
-> rpl::producer<FieldAutocomplete::BotCommandChosen> {
return _botCommandChosen.events();
}
auto FieldAutocompleteInner::stickerChosen() const
auto FieldAutocomplete::Inner::stickerChosen() const
-> rpl::producer<FieldAutocomplete::StickerChosen> {
return _stickerChosen.events();
}
auto FieldAutocompleteInner::scrollToRequested() const
auto FieldAutocomplete::Inner::scrollToRequested() const
-> rpl::producer<ScrollTo> {
return _scrollToRequested.events();
}
} // namespace internal

View File

@@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Ui {
class PopupMenu;
class ScrollArea;
class InputField;
} // namespace Ui
namespace Lottie {
@@ -32,42 +33,15 @@ class DocumentMedia;
class CloudImageView;
} // namespace Data
namespace internal {
struct StickerSuggestion {
not_null<DocumentData*> document;
std::shared_ptr<Data::DocumentMedia> documentMedia;
std::unique_ptr<Lottie::SinglePlayer> animated;
};
struct MentionRow {
not_null<UserData*> user;
std::shared_ptr<Data::CloudImageView> userpic;
};
struct BotCommandRow {
not_null<UserData*> user;
not_null<const BotCommand*> command;
std::shared_ptr<Data::CloudImageView> userpic;
};
using HashtagRows = std::vector<QString>;
using BotCommandRows = std::vector<BotCommandRow>;
using StickerRows = std::vector<StickerSuggestion>;
using MentionRows = std::vector<MentionRow>;
class FieldAutocompleteInner;
} // namespace internal
class FieldAutocomplete final : public Ui::RpWidget {
public:
FieldAutocomplete(
QWidget *parent,
not_null<Window::SessionController*> controller);
~FieldAutocomplete();
[[nodiscard]] not_null<Window::SessionController*> controller() const;
bool clearFilteredBotCommands();
void showFiltered(
not_null<PeerData*> peer,
@@ -140,29 +114,54 @@ protected:
void paintEvent(QPaintEvent *e) override;
private:
class Inner;
friend class Inner;
struct StickerSuggestion {
not_null<DocumentData*> document;
std::shared_ptr<Data::DocumentMedia> documentMedia;
std::unique_ptr<Lottie::SinglePlayer> animated;
};
struct MentionRow {
not_null<UserData*> user;
std::shared_ptr<Data::CloudImageView> userpic;
};
struct BotCommandRow {
not_null<UserData*> user;
not_null<const BotCommand*> command;
std::shared_ptr<Data::CloudImageView> userpic;
};
using HashtagRows = std::vector<QString>;
using BotCommandRows = std::vector<BotCommandRow>;
using StickerRows = std::vector<StickerSuggestion>;
using MentionRows = std::vector<MentionRow>;
void animationCallback();
void hideFinish();
void updateFiltered(bool resetScroll = false);
void recount(bool resetScroll = false);
internal::StickerRows getStickerSuggestions();
StickerRows getStickerSuggestions();
const not_null<Window::SessionController*> _controller;
QPixmap _cache;
internal::MentionRows _mrows;
internal::HashtagRows _hrows;
internal::BotCommandRows _brows;
internal::StickerRows _srows;
MentionRows _mrows;
HashtagRows _hrows;
BotCommandRows _brows;
StickerRows _srows;
void rowsUpdated(
internal::MentionRows &&mrows,
internal::HashtagRows &&hrows,
internal::BotCommandRows &&brows,
internal::StickerRows &&srows,
MentionRows &&mrows,
HashtagRows &&hrows,
BotCommandRows &&brows,
StickerRows &&srows,
bool resetScroll);
object_ptr<Ui::ScrollArea> _scroll;
QPointer<internal::FieldAutocompleteInner> _inner;
QPointer<Inner> _inner;
ChatData *_chat = nullptr;
UserData *_user = nullptr;
@@ -186,100 +185,4 @@ private:
Fn<bool(int)> _moderateKeyActivateCallback;
friend class internal::FieldAutocompleteInner;
};
namespace internal {
class FieldAutocompleteInner final
: public Ui::RpWidget
, private base::Subscriber {
public:
struct ScrollTo {
int top;
int bottom;
};
FieldAutocompleteInner(
not_null<Window::SessionController*> controller,
not_null<FieldAutocomplete*> parent,
not_null<MentionRows*> mrows,
not_null<HashtagRows*> hrows,
not_null<BotCommandRows*> brows,
not_null<StickerRows*> srows);
void clearSel(bool hidden = false);
bool moveSel(int key);
bool chooseSelected(FieldAutocomplete::ChooseMethod method) const;
bool chooseAtIndex(
FieldAutocomplete::ChooseMethod method,
int index,
Api::SendOptions options = Api::SendOptions()) const;
void setRecentInlineBotsInRows(int32 bots);
void rowsUpdated();
rpl::producer<FieldAutocomplete::MentionChosen> mentionChosen() const;
rpl::producer<FieldAutocomplete::HashtagChosen> hashtagChosen() const;
rpl::producer<FieldAutocomplete::BotCommandChosen>
botCommandChosen() const;
rpl::producer<FieldAutocomplete::StickerChosen> stickerChosen() const;
rpl::producer<ScrollTo> scrollToRequested() const;
void onParentGeometryChanged();
private:
void paintEvent(QPaintEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
void enterEventHook(QEvent *e) override;
void leaveEventHook(QEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
void contextMenuEvent(QContextMenuEvent *e) override;
void updateSelectedRow();
void setSel(int sel, bool scroll = false);
void showPreview();
void selectByMouse(QPoint global);
QSize stickerBoundingBox() const;
void setupLottie(StickerSuggestion &suggestion);
void repaintSticker(not_null<DocumentData*> document);
std::shared_ptr<Lottie::FrameRenderer> getLottieRenderer();
const not_null<Window::SessionController*> _controller;
const not_null<FieldAutocomplete*> _parent;
const not_null<MentionRows*> _mrows;
const not_null<HashtagRows*> _hrows;
const not_null<BotCommandRows*> _brows;
const not_null<StickerRows*> _srows;
rpl::lifetime _stickersLifetime;
std::weak_ptr<Lottie::FrameRenderer> _lottieRenderer;
base::unique_qptr<Ui::PopupMenu> _menu;
int _stickersPerRow = 1;
int _recentInlineBotsInRows = 0;
int _sel = -1;
int _down = -1;
std::optional<QPoint> _lastMousePosition;
bool _mouseSelection = false;
bool _overDelete = false;
bool _previewShown = false;
rpl::event_stream<FieldAutocomplete::MentionChosen> _mentionChosen;
rpl::event_stream<FieldAutocomplete::HashtagChosen> _hashtagChosen;
rpl::event_stream<FieldAutocomplete::BotCommandChosen> _botCommandChosen;
rpl::event_stream<FieldAutocomplete::StickerChosen> _stickerChosen;
rpl::event_stream<ScrollTo> _scrollToRequested;
base::Timer _previewTimer;
};
} // namespace internal