diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index 99c4f78239..b23e242ae8 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -535,7 +535,7 @@ autoDownloadTitlePosition: point(23px, 18px); autoDownloadTitleFont: font(15px semibold); confirmCaptionArea: InputField(defaultInputField) { - textMargins: margins(1px, 26px, 1px, 4px); + textMargins: margins(1px, 26px, 31px, 4px); heightMax: 78px; } confirmBg: windowBgOver; diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.cpp b/Telegram/SourceFiles/boxes/edit_caption_box.cpp index 1b33c376ec..dd4ff36a20 100644 --- a/Telegram/SourceFiles/boxes/edit_caption_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_caption_box.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/input_fields.h" #include "ui/image/image.h" #include "ui/text_options.h" +#include "ui/special_buttons.h" #include "media/media_clip_reader.h" #include "history/history.h" #include "history/history_item.h" @@ -17,7 +18,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_photo.h" #include "data/data_document.h" #include "lang/lang_keys.h" +#include "core/event_filter.h" #include "chat_helpers/message_field.h" +#include "chat_helpers/tabbed_panel.h" +#include "chat_helpers/tabbed_selector.h" #include "chat_helpers/emoji_suggestions_widget.h" #include "window/window_controller.h" #include "mainwidget.h" @@ -25,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "auth_session.h" #include "styles/style_history.h" #include "styles/style_boxes.h" +#include "styles/style_chat_helpers.h" EditCaptionBox::EditCaptionBox( QWidget*, @@ -219,6 +224,25 @@ EditCaptionBox::EditCaptionBox( _field->setEditLinkCallback(DefaultEditLinkCallback(_field)); } +bool EditCaptionBox::emojiFilter(not_null event) { + const auto type = event->type(); + if (type == QEvent::Move || type == QEvent::Resize) { + // updateEmojiPanelGeometry uses not only container geometry, but + // also container children geometries that will be updated later. + crl::on_main(this, [=] { updateEmojiPanelGeometry(); }); + } + return false; +} + +void EditCaptionBox::updateEmojiPanelGeometry() { + const auto parent = _emojiPanel->parentWidget(); + const auto global = _emojiToggle->mapToGlobal({ 0, 0 }); + const auto local = parent->mapFromGlobal(global); + _emojiPanel->moveBottomRight( + local.y(), + local.x() + _emojiToggle->width() * 3); +} + void EditCaptionBox::prepareGifPreview(not_null document) { if (_gifPreview) { return; @@ -266,6 +290,8 @@ void EditCaptionBox::prepare() { getDelegate()->outerContainer(), _field); + setupEmojiPanel(); + auto cursor = _field->textCursor(); cursor.movePosition(QTextCursor::End); _field->setTextCursor(cursor); @@ -274,9 +300,40 @@ void EditCaptionBox::prepare() { void EditCaptionBox::captionResized() { updateBoxSize(); resizeEvent(0); + updateEmojiPanelGeometry(); update(); } +void EditCaptionBox::setupEmojiPanel() { + const auto container = getDelegate()->outerContainer(); + _emojiPanel = base::make_unique_q( + container, + _controller, + object_ptr( + nullptr, + _controller, + ChatHelpers::TabbedSelector::Mode::EmojiOnly)); + _emojiPanel->setDesiredHeightValues( + 1., + st::emojiPanMinHeight / 2, + st::emojiPanMinHeight); + _emojiPanel->hide(); + _emojiPanel->getSelector()->emojiChosen( + ) | rpl::start_with_next([=](EmojiPtr emoji) { + Ui::InsertEmojiAtCursor(_field->textCursor(), emoji); + }, lifetime()); + + _emojiFilter.reset(Core::InstallEventFilter( + container, + [=](not_null event) { return emojiFilter(event); })); + + _emojiToggle.create(this, st::boxAttachEmoji); + _emojiToggle->installEventFilter(_emojiPanel); + _emojiToggle->addClickHandler([=] { + _emojiPanel->toggleAnimated(); + }); +} + void EditCaptionBox::updateBoxSize() { auto newHeight = st::boxPhotoPadding.top() + st::boxPhotoCaptionSkip + _field->height() + errorTopSkip() + st::normalFont->height; if (_photo || _animated) { @@ -393,6 +450,11 @@ void EditCaptionBox::resizeEvent(QResizeEvent *e) { BoxContent::resizeEvent(e); _field->resize(st::sendMediaPreviewSize, _field->height()); _field->moveToLeft(st::boxPhotoPadding.left(), height() - st::normalFont->height - errorTopSkip() - _field->height()); + _emojiToggle->moveToLeft( + (st::boxPhotoPadding.left() + + st::sendMediaPreviewSize + - _emojiToggle->width()), + _field->y() + st::boxAttachEmojiTop); } void EditCaptionBox::setInnerFocus() { diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.h b/Telegram/SourceFiles/boxes/edit_caption_box.h index eadb149fbf..7f9c4788a5 100644 --- a/Telegram/SourceFiles/boxes/edit_caption_box.h +++ b/Telegram/SourceFiles/boxes/edit_caption_box.h @@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/abstract_box.h" +namespace ChatHelpers { +class TabbedPanel; +} // namespace ChatHelpers + namespace Window { class Controller; } // namespace Window @@ -19,6 +23,7 @@ class Media; namespace Ui { class InputField; +class EmojiButton; } // namespace Ui namespace Window { @@ -44,6 +49,10 @@ private: void prepareGifPreview(not_null document); void clipCallback(Media::Clip::Notification notification); + void setupEmojiPanel(); + void updateEmojiPanelGeometry(); + bool emojiFilter(not_null event); + void save(); void captionResized(); @@ -65,6 +74,9 @@ private: Media::Clip::ReaderPointer _gifPreview; object_ptr _field = { nullptr }; + object_ptr _emojiToggle = { nullptr }; + base::unique_qptr _emojiPanel; + base::unique_qptr _emojiFilter; int _thumbx = 0; int _thumbw = 0; diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index e8ef0a543a..734f2ca3ef 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -14,8 +14,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_media_types.h" #include "chat_helpers/message_field.h" #include "chat_helpers/emoji_suggestions_widget.h" +#include "chat_helpers/tabbed_panel.h" +#include "chat_helpers/tabbed_selector.h" #include "core/file_utilities.h" #include "core/mime_type.h" +#include "core/event_filter.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/buttons.h" #include "ui/widgets/input_fields.h" @@ -23,11 +26,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/wrap/fade_wrap.h" #include "ui/grouped_layout.h" #include "ui/text_options.h" +#include "ui/special_buttons.h" #include "media/media_clip_reader.h" #include "window/window_controller.h" +#include "layout.h" #include "styles/style_history.h" #include "styles/style_boxes.h" -#include "layout.h" +#include "styles/style_chat_helpers.h" namespace { @@ -1587,11 +1592,63 @@ void SendFilesBox::setupCaption() { Ui::Emoji::SuggestionsController::Init( getDelegate()->outerContainer(), _caption); + + setupEmojiPanel(); +} + +void SendFilesBox::setupEmojiPanel() { + const auto container = getDelegate()->outerContainer(); + _emojiPanel = base::make_unique_q( + container, + _controller, + object_ptr( + nullptr, + _controller, + ChatHelpers::TabbedSelector::Mode::EmojiOnly)); + _emojiPanel->setDesiredHeightValues( + 1., + st::emojiPanMinHeight / 2, + st::emojiPanMinHeight); + _emojiPanel->hide(); + _emojiPanel->getSelector()->emojiChosen( + ) | rpl::start_with_next([=](EmojiPtr emoji) { + Ui::InsertEmojiAtCursor(_caption->textCursor(), emoji); + }, lifetime()); + + _emojiFilter.reset(Core::InstallEventFilter( + container, + [=](not_null event) { return emojiFilter(event); })); + + _emojiToggle.create(this, st::boxAttachEmoji); + _emojiToggle->installEventFilter(_emojiPanel); + _emojiToggle->addClickHandler([=] { + _emojiPanel->toggleAnimated(); + }); +} + +bool SendFilesBox::emojiFilter(not_null event) { + const auto type = event->type(); + if (type == QEvent::Move || type == QEvent::Resize) { + // updateEmojiPanelGeometry uses not only container geometry, but + // also container children geometries that will be updated later. + crl::on_main(this, [=] { updateEmojiPanelGeometry(); }); + } + return false; +} + +void SendFilesBox::updateEmojiPanelGeometry() { + const auto parent = _emojiPanel->parentWidget(); + const auto global = _emojiToggle->mapToGlobal({ 0, 0 }); + const auto local = parent->mapFromGlobal(global); + _emojiPanel->moveBottomRight( + local.y(), + local.x() + _emojiToggle->width() * 3); } void SendFilesBox::captionResized() { updateBoxSize(); updateControlsGeometry(); + updateEmojiPanelGeometry(); update(); } @@ -1747,6 +1804,12 @@ void SendFilesBox::updateControlsGeometry() { st::boxPhotoPadding.left(), bottom - _caption->height()); bottom -= st::boxPhotoCaptionSkip + _caption->height(); + + _emojiToggle->moveToLeft( + (st::boxPhotoPadding.left() + + st::sendMediaPreviewSize + - _emojiToggle->width()), + _caption->y() + st::boxAttachEmojiTop); } const auto pointers = { _sendAlbum.data(), diff --git a/Telegram/SourceFiles/boxes/send_files_box.h b/Telegram/SourceFiles/boxes/send_files_box.h index 4839692973..b8b7c03276 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.h +++ b/Telegram/SourceFiles/boxes/send_files_box.h @@ -16,6 +16,10 @@ namespace Window { class Controller; } // namespace Window +namespace ChatHelpers { +class TabbedPanel; +} // namespace ChatHelpers + namespace Ui { template class Radioenum; @@ -24,6 +28,7 @@ class RadioenumGroup; class RoundButton; class InputField; struct GroupMediaLayout; +class EmojiButton; } // namespace Ui namespace Window { @@ -80,6 +85,10 @@ private: not_null wrap, not_null content); + void setupEmojiPanel(); + void updateEmojiPanelGeometry(); + bool emojiFilter(not_null event); + void refreshAlbumMediaCount(); void preparePreview(); void prepareSingleFilePreview(); @@ -116,6 +125,10 @@ private: bool _confirmed = false; object_ptr _caption = { nullptr }; + object_ptr _emojiToggle = { nullptr }; + base::unique_qptr _emojiPanel; + base::unique_qptr _emojiFilter; + object_ptr> _sendAlbum = { nullptr }; object_ptr> _sendPhotos = { nullptr }; object_ptr> _sendFiles = { nullptr }; diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index 9d4ba12f5d..de34ca354b 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -151,6 +151,9 @@ emojiPanHover: windowBgOver; emojiPanSlideDuration: 200; emojiPanDesiredSize: 45px; +inlineResultsMinHeight: 278px; +inlineResultsMaxHeight: 640px; + emojiPanHeader: 42px; emojiPanHeaderFont: semiboldFont; emojiPanHeaderLeft: 22px; diff --git a/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp b/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp index e310a0c4ef..fd4a7a9b83 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp @@ -679,9 +679,13 @@ bool SuggestionsController::outerFilter(not_null event) { switch (type) { case QEvent::Move: case QEvent::Resize: { - if (_shown) { - updateGeometry(); - } + // updateGeometry uses not only container geometry, but also + // container children geometries that will be updated later. + InvokeQueued(_container, [=] { + if (_shown) { + updateGeometry(); + } + }); } break; } return false; diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_panel.cpp b/Telegram/SourceFiles/chat_helpers/tabbed_panel.cpp index 02524349fd..2f9c8c4122 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_panel.cpp +++ b/Telegram/SourceFiles/chat_helpers/tabbed_panel.cpp @@ -38,7 +38,10 @@ TabbedPanel::TabbedPanel( object_ptr selector) : RpWidget(parent) , _controller(controller) -, _selector(std::move(selector)) { +, _selector(std::move(selector)) +, _heightRatio(st::emojiPanHeightRatio) +, _minContentHeight(st::emojiPanMinHeight) +, _maxContentHeight(st::emojiPanMaxHeight) { _selector->setParent(this); _selector->setRoundRadius(st::buttonRadius); _selector->setAfterShownCallback([this](SelectorTab tab) { @@ -97,8 +100,19 @@ TabbedPanel::TabbedPanel( hideChildren(); } -void TabbedPanel::moveBottom(int bottom) { +void TabbedPanel::moveBottomRight(int bottom, int right) { _bottom = bottom; + _right = right; + updateContentHeight(); +} + +void TabbedPanel::setDesiredHeightValues( + float64 ratio, + int minHeight, + int maxHeight) { + _heightRatio = ratio; + _minContentHeight = minHeight; + _maxContentHeight = maxHeight; updateContentHeight(); } @@ -110,8 +124,11 @@ void TabbedPanel::updateContentHeight() { auto addedHeight = innerPadding().top() + innerPadding().bottom(); auto marginsHeight = _selector->marginTop() + _selector->marginBottom(); auto availableHeight = _bottom - marginsHeight; - auto wantedContentHeight = qRound(st::emojiPanHeightRatio * availableHeight) - addedHeight; - auto contentHeight = marginsHeight + snap(wantedContentHeight, st::emojiPanMinHeight, st::emojiPanMaxHeight); + auto wantedContentHeight = qRound(_heightRatio * availableHeight) - addedHeight; + auto contentHeight = marginsHeight + snap( + wantedContentHeight, + _minContentHeight, + _maxContentHeight); auto resultTop = _bottom - addedHeight - contentHeight; if (contentHeight == _contentHeight) { move(x(), resultTop); @@ -169,7 +186,8 @@ void TabbedPanel::paintEvent(QPaintEvent *e) { } void TabbedPanel::moveByBottom() { - moveToRight(0, y()); + const auto right = std::max(parentWidget()->width() - _right, 0); + moveToRight(right, y()); updateContentHeight(); } @@ -369,6 +387,7 @@ void TabbedPanel::showStarted() { if (isHidden()) { _selector->showStarted(); moveByBottom(); + raise(); show(); startShowAnimation(); } else if (_hiding) { diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_panel.h b/Telegram/SourceFiles/chat_helpers/tabbed_panel.h index 31a7317354..b398c82296 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_panel.h +++ b/Telegram/SourceFiles/chat_helpers/tabbed_panel.h @@ -25,11 +25,18 @@ class TabbedSelector; class TabbedPanel : public Ui::RpWidget { public: TabbedPanel(QWidget *parent, not_null controller); - TabbedPanel(QWidget *parent, not_null controller, object_ptr selector); + TabbedPanel( + QWidget *parent, + not_null controller, + object_ptr selector); object_ptr takeSelector(); QPointer getSelector() const; - void moveBottom(int bottom); + void moveBottomRight(int bottom, int right); + void setDesiredHeightValues( + float64 ratio, + int minHeight, + int maxHeight); void hideFast(); bool hiding() const { @@ -86,6 +93,10 @@ private: int _contentMaxHeight = 0; int _contentHeight = 0; int _bottom = 0; + int _right = 0; + float64 _heightRatio = 1.; + int _minContentHeight = 0; + int _maxContentHeight = 0; std::unique_ptr _showAnimation; Animation _a_show; diff --git a/Telegram/SourceFiles/history/history.style b/Telegram/SourceFiles/history/history.style index 4c0d353a9c..4d4aa94846 100644 --- a/Telegram/SourceFiles/history/history.style +++ b/Telegram/SourceFiles/history/history.style @@ -213,7 +213,7 @@ historyAttach: IconButton { historyAttachEmoji: IconButton(historyAttach) { icon: icon {{ "send_control_emoji", historyComposeIconFg }}; iconOver: icon {{ "send_control_emoji", historyComposeIconFgOver }}; - iconPosition: point(15px, 15px); + iconPosition: point(-1px, -1px); } historyAttachEmojiFgActive: windowActiveTextFg; historyAttachEmojiActive: icon {{ "send_control_emoji", historyAttachEmojiFgActive }}; @@ -478,3 +478,10 @@ historyAboutProxyPadding: margins(20px, 10px, 20px, 10px); historyMapPoint: icon {{ "map_point", mapPointDrop }}; historyMapPointInner: icon {{ "map_point_inner", mapPointDot }}; + +boxAttachEmoji: IconButton(historyAttachEmoji) { + width: 30px; + height: 30px; + rippleAreaSize: 0px; +} +boxAttachEmojiTop: 20px; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 6abc193f78..ce8f1f1449 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -4091,7 +4091,7 @@ void HistoryWidget::moveFieldControls() { _inlineResults->moveBottom(_field->y() - st::historySendPadding); } if (_tabbedPanel) { - _tabbedPanel->moveBottom(buttonsBottom); + _tabbedPanel->moveBottomRight(buttonsBottom, width()); } auto fullWidthButtonRect = myrtlrect( diff --git a/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp b/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp index 798b8fe574..ca6db5e1ee 100644 --- a/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp @@ -43,7 +43,7 @@ Inner::Inner(QWidget *parent, not_null controller) : TWidge , _controller(controller) , _updateInlineItems([=] { updateInlineItems(); }) , _previewTimer([=] { showPreview(); }) { - resize(st::emojiPanWidth - st::emojiScroll.width - st::buttonRadius, st::emojiPanMinHeight); + resize(st::emojiPanWidth - st::emojiScroll.width - st::buttonRadius, st::inlineResultsMinHeight); setMouseTracking(true); setAttribute(Qt::WA_OpaquePaintEvent); @@ -760,7 +760,7 @@ void Widget::moveBottom(int bottom) { void Widget::updateContentHeight() { auto addedHeight = innerPadding().top() + innerPadding().bottom(); auto wantedContentHeight = qRound(st::emojiPanHeightRatio * _bottom) - addedHeight; - auto contentHeight = snap(wantedContentHeight, st::emojiPanMinHeight, st::emojiPanMaxHeight); + auto contentHeight = snap(wantedContentHeight, st::inlineResultsMinHeight, st::inlineResultsMaxHeight); accumulate_min(contentHeight, _bottom - addedHeight); accumulate_min(contentHeight, _contentMaxHeight); auto resultTop = _bottom - addedHeight - contentHeight; diff --git a/Telegram/SourceFiles/ui/special_buttons.cpp b/Telegram/SourceFiles/ui/special_buttons.cpp index e5fb19d96b..888840ee9d 100644 --- a/Telegram/SourceFiles/ui/special_buttons.cpp +++ b/Telegram/SourceFiles/ui/special_buttons.cpp @@ -140,9 +140,9 @@ QPoint HistoryDownButton::prepareRippleStartPosition() const { void HistoryDownButton::paintEvent(QPaintEvent *e) { Painter p(this); - auto ms = getms(); - auto over = isOver(); - auto down = isDown(); + const auto ms = getms(); + const auto over = isOver(); + const auto down = isDown(); ((over || down) ? _st.iconBelowOver : _st.iconBelow).paint(p, _st.iconPosition, width()); paintRipple(p, _st.rippleAreaPosition.x(), _st.rippleAreaPosition.y(), ms); ((over || down) ? _st.iconAboveOver : _st.iconAbove).paint(p, _st.iconPosition, width()); @@ -190,13 +190,20 @@ void EmojiButton::paintEvent(QPaintEvent *e) { if (loadingState.shown < 1.) { p.setOpacity(1. - loadingState.shown); - auto icon = _iconOverride ? _iconOverride : &(over ? _st.iconOver : _st.icon); - icon->paint(p, _st.iconPosition, width()); + const auto icon = _iconOverride ? _iconOverride : &(over ? _st.iconOver : _st.icon); + auto position = _st.iconPosition; + if (position.x() < 0) { + position.setX((width() - icon->width()) / 2); + } + if (position.y() < 0) { + position.setY((height() - icon->height()) / 2); + } + icon->paint(p, position, width()); p.setOpacity(1.); } - QRect inner(QPoint((width() - st::historyEmojiCircle.width()) / 2, st::historyEmojiCircleTop), st::historyEmojiCircle); + QRect inner(QPoint((width() - st::historyEmojiCircle.width()) / 2, (height() - st::historyEmojiCircle.height()) / 2), st::historyEmojiCircle); const auto color = (_colorOverride ? *_colorOverride : (over @@ -261,6 +268,9 @@ void EmojiButton::onStateChanged(State was, StateChangeSource source) { } QPoint EmojiButton::prepareRippleStartPosition() const { + if (!_st.rippleAreaSize) { + return DisabledRippleStartPosition(); + } return mapFromGlobal(QCursor::pos()) - _st.rippleAreaPosition; }