/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/effects/animations.h" #include "ui/rp_widget.h" #include "base/unique_qptr.h" #include "base/timer.h" #include namespace Main { class Session; } // namespace Main namespace Ui { class InnerDropdown; class InputField; } // namespace Ui namespace Ui::Text { class CustomEmoji; } // namespace Ui::Text namespace Ui::Emoji { using SuggestionsQuery = std::variant; class SuggestionsWidget final : public Ui::RpWidget { public: SuggestionsWidget( QWidget *parent, not_null session, bool suggestCustomEmoji, Fn)> allowCustomWithoutPremium); ~SuggestionsWidget(); void showWithQuery(SuggestionsQuery query, bool force = false); void selectFirstResult(); bool handleKeyEvent(int key); [[nodiscard]] rpl::producer toggleAnimated() const; struct Chosen { QString emoji; QString customData; }; [[nodiscard]] rpl::producer triggered() const; private: struct Row { Row(not_null emoji, const QString &replacement); Ui::Text::CustomEmoji *custom = nullptr; DocumentData *document = nullptr; not_null emoji; QString replacement; }; struct Custom { not_null document; not_null emoji; QString replacement; }; bool eventHook(QEvent *e) override; void paintEvent(QPaintEvent *e) override; void keyPressEvent(QKeyEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void enterEventHook(QEnterEvent *e) override; void leaveEventHook(QEvent *e) override; void scrollByWheelEvent(not_null e); void paintFadings(Painter &p) const; [[nodiscard]] std::vector getRowsByQuery(const QString &text) const; [[nodiscard]] base::flat_multi_map lookupCustom( const std::vector &rows) const; [[nodiscard]] std::vector appendCustom( std::vector rows); [[nodiscard]] std::vector appendCustom( std::vector rows, const base::flat_multi_map &custom); void resizeToRows(); void setSelected( int selected, anim::type animated = anim::type::instant); void setPressed(int pressed); void clearMouseSelection(); void clearSelection(); void updateSelectedItem(); void updateItem(int index); [[nodiscard]] QRect inner() const; [[nodiscard]] QPoint innerShift() const; [[nodiscard]] QPoint mapToInner(QPoint globalPosition) const; void selectByMouse(QPoint globalPosition); bool triggerSelectedRow() const; void triggerRow(const Row &row) const; [[nodiscard]] int scrollCurrent() const; void scrollTo(int value, anim::type animated = anim::type::instant); void stopAnimations(); [[nodiscard]] not_null resolveCustomEmoji( not_null document); void customEmojiRepaint(); const not_null _session; SuggestionsQuery _query; std::vector _rows; bool _suggestCustomEmoji = false; Fn)> _allowCustomWithoutPremium; base::flat_map< not_null, std::unique_ptr> _customEmoji; bool _repaintScheduled = false; std::optional _lastMousePosition; bool _mouseSelection = false; int _selected = -1; int _pressed = -1; int _scrollValue = 0; Ui::Animations::Simple _scrollAnimation; Ui::Animations::Simple _selectedAnimation; int _scrollMax = 0; int _oneWidth = 0; QMargins _padding; QPoint _mousePressPosition; int _dragScrollStart = -1; rpl::event_stream _toggleAnimated; rpl::event_stream _triggered; }; class SuggestionsController { public: struct Options { bool suggestExactFirstWord = true; bool suggestCustomEmoji = false; Fn)> allowCustomWithoutPremium; }; SuggestionsController( not_null outer, not_null field, not_null session, const Options &options); void raise(); void setReplaceCallback(Fn callback); static not_null Init( not_null outer, not_null field, not_null session) { return Init(outer, field, session, {}); } static not_null Init( not_null outer, not_null field, not_null session, const Options &options); private: void handleCursorPositionChange(); void handleTextChange(); void showWithQuery(SuggestionsQuery query); [[nodiscard]] SuggestionsQuery getEmojiQuery(); void suggestionsUpdated(bool visible); void updateGeometry(); void updateForceHidden(); void replaceCurrent( const QString &replacement, const QString &customEmojiData); bool fieldFilter(not_null event); bool outerFilter(not_null event); bool _shown = false; bool _forceHidden = false; int _queryStartPosition = 0; int _emojiQueryLength = 0; bool _ignoreCursorPositionChange = false; bool _textChangeAfterKeyPress = false; QPointer _field; const not_null _session; Fn _replaceCallback; base::unique_qptr _container; QPointer _suggestions; base::unique_qptr _fieldFilter; base::unique_qptr _outerFilter; base::Timer _showExactTimer; bool _keywordsRefreshed = false; SuggestionsQuery _lastShownQuery; Options _options; rpl::lifetime _lifetime; }; } // namespace Ui::Emoji