From c803603de416ee99c248f4813c09d3885013cfe6 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 19 Apr 2024 02:23:55 +0300 Subject: [PATCH] Added ability to insert custom emoji to polls. --- Telegram/SourceFiles/boxes/boxes.style | 7 +- .../SourceFiles/boxes/create_poll_box.cpp | 263 +++++++++++++++--- Telegram/SourceFiles/boxes/create_poll_box.h | 5 + 3 files changed, 235 insertions(+), 40 deletions(-) diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index 582468070..6d3d639ba 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -691,6 +691,10 @@ createPollOptionField: InputField(createPollField) { placeholderMargins: margins(2px, 0px, 2px, 0px); heightMax: 68px; } +createPollOptionFieldPremium: InputField(createPollOptionField) { + textMargins: margins(22px, 11px, 68px, 11px); +} +createPollOptionFieldPremiumEmojiPosition: point(15px, -1px); createPollSolutionField: InputField(createPollField) { textMargins: margins(0px, 4px, 0px, 4px); border: 1px; @@ -704,7 +708,7 @@ createPollOptionRemove: CrossButton { cross: CrossAnimation { size: 22px; skip: 6px; - stroke: 1.; + stroke: 1.5; minScale: 0.3; } crossFg: boxTitleCloseFg; @@ -718,6 +722,7 @@ createPollOptionRemove: CrossButton { } } createPollOptionRemovePosition: point(11px, 9px); +createPollOptionEmojiPositionSkip: 4px; createPollWarning: FlatLabel(defaultFlatLabel) { textFg: windowSubTextFg; palette: TextPalette(defaultTextPalette) { diff --git a/Telegram/SourceFiles/boxes/create_poll_box.cpp b/Telegram/SourceFiles/boxes/create_poll_box.cpp index 7ec93239b..1d02b5e52 100644 --- a/Telegram/SourceFiles/boxes/create_poll_box.cpp +++ b/Telegram/SourceFiles/boxes/create_poll_box.cpp @@ -7,33 +7,41 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/create_poll_box.h" -#include "lang/lang_keys.h" -#include "data/data_poll.h" -#include "ui/toast/toast.h" -#include "ui/wrap/vertical_layout.h" -#include "ui/wrap/slide_wrap.h" -#include "ui/wrap/fade_wrap.h" -#include "ui/widgets/fields/input_field.h" -#include "ui/widgets/shadow.h" -#include "ui/widgets/labels.h" -#include "ui/widgets/buttons.h" -#include "ui/widgets/checkbox.h" -#include "ui/text/text_utilities.h" -#include "ui/vertical_list.h" -#include "main/main_session.h" -#include "core/application.h" -#include "core/core_settings.h" +#include "base/call_delayed.h" +#include "base/event_filter.h" +#include "base/random.h" +#include "base/unique_qptr.h" #include "chat_helpers/emoji_suggestions_widget.h" #include "chat_helpers/message_field.h" -#include "menu/menu_send.h" +#include "chat_helpers/tabbed_panel.h" +#include "chat_helpers/tabbed_selector.h" +#include "core/application.h" +#include "core/core_settings.h" +#include "data/data_poll.h" +#include "data/data_user.h" +#include "data/stickers/data_custom_emoji.h" #include "history/view/history_view_schedule_box.h" -#include "base/unique_qptr.h" -#include "base/event_filter.h" -#include "base/call_delayed.h" -#include "base/random.h" +#include "lang/lang_keys.h" +#include "main/main_session.h" +#include "menu/menu_send.h" +#include "ui/controls/emoji_button.h" +#include "ui/rect.h" +#include "ui/text/text_utilities.h" +#include "ui/toast/toast.h" +#include "ui/vertical_list.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/checkbox.h" +#include "ui/widgets/fields/input_field.h" +#include "ui/widgets/labels.h" +#include "ui/widgets/shadow.h" +#include "ui/wrap/fade_wrap.h" +#include "ui/wrap/slide_wrap.h" +#include "ui/wrap/vertical_layout.h" #include "window/window_session_controller.h" -#include "styles/style_layers.h" #include "styles/style_boxes.h" +#include "styles/style_chat_helpers.h" // defaultComposeFiles. +#include "styles/style_layers.h" +#include "styles/style_settings.h" namespace { @@ -46,12 +54,107 @@ constexpr auto kSolutionLimit = 200; constexpr auto kWarnSolutionLimit = 60; constexpr auto kErrorLimit = 99; +[[nodiscard]] not_null AddEmojiToggleToField( + not_null field, + not_null box, + not_null controller, + not_null emojiPanel, + QPoint shift) { + const auto emojiToggle = Ui::CreateChild( + field->parentWidget(), + st::defaultComposeFiles.emoji); + const auto fade = Ui::CreateChild( + emojiToggle, + emojiToggle, + 0.5); + { + const auto fadeTarget = Ui::CreateChild(emojiToggle); + fadeTarget->resize(emojiToggle->size()); + fadeTarget->paintRequest( + ) | rpl::start_with_next([=](const QRect &rect) { + auto p = QPainter(fadeTarget); + if (fade->animating()) { + p.fillRect(fadeTarget->rect(), st::boxBg); + } + fade->paint(p); + }, fadeTarget->lifetime()); + rpl::single(false) | rpl::then( + field->focusedChanges() + ) | rpl::start_with_next([=](bool shown) { + if (shown) { + fade->fadeIn(st::universalDuration); + } else { + fade->fadeOut(st::universalDuration); + } + }, emojiToggle->lifetime()); + fade->fadeOut(1); + fade->finish(); + } + + + const auto outer = box->getDelegate()->outerContainer(); + const auto allow = [](not_null) { return true; }; + InitMessageFieldHandlers( + controller, + field, + Window::GifPauseReason::Layer, + allow); + Ui::Emoji::SuggestionsController::Init( + outer, + field, + &controller->session(), + Ui::Emoji::SuggestionsController::Options{ + .suggestCustomEmoji = true, + .allowCustomWithoutPremium = allow, + }); + const auto updateEmojiPanelGeometry = [=] { + const auto parent = emojiPanel->parentWidget(); + const auto global = emojiToggle->mapToGlobal({ 0, 0 }); + const auto local = parent->mapFromGlobal(global); + const auto right = local.x() + emojiToggle->width() * 3; + const auto isDropDown = local.y() < parent->height() / 2; + emojiPanel->setDropDown(isDropDown); + if (isDropDown) { + emojiPanel->moveTopRight( + local.y() + emojiToggle->height(), + right); + } else { + emojiPanel->moveBottomRight(local.y(), right); + } + }; + rpl::combine( + box->sizeValue(), + field->geometryValue() + ) | rpl::start_with_next([=](QSize outer, QRect inner) { + emojiToggle->moveToLeft( + rect::right(inner) + shift.x(), + inner.y() + shift.y()); + emojiToggle->update(); + }, emojiToggle->lifetime()); + + emojiToggle->installEventFilter(emojiPanel); + emojiToggle->addClickHandler([=] { + updateEmojiPanelGeometry(); + emojiPanel->toggleAnimated(); + }); + const auto filterCallback = [=](not_null event) { + if (event->type() == QEvent::Enter) { + updateEmojiPanelGeometry(); + } + return base::EventFilterResult::Continue; + }; + base::install_event_filter(emojiToggle, filterCallback); + + return emojiToggle; +} + class Options { public: Options( - not_null outer, + not_null box, not_null container, - not_null session, + not_null controller, + ChatHelpers::TabbedPanel *emojiPanel, bool chooseCorrectEnabled); [[nodiscard]] bool hasOptions() const; @@ -140,9 +243,10 @@ private: [[nodiscard]] auto createChooseCorrectGroup() -> std::shared_ptr; - not_null _outer; + not_null _box; not_null _container; - const not_null _session; + const not_null _controller; + ChatHelpers::TabbedPanel * const _emojiPanel; std::shared_ptr _chooseCorrectGroup; int _position = 0; std::vector> _list; @@ -154,6 +258,7 @@ private: rpl::event_stream> _scrollToWidget; rpl::event_stream<> _backspaceInFront; rpl::event_stream<> _tabbed; + rpl::lifetime _emojiPanelLifetime; }; @@ -193,8 +298,9 @@ not_null CreateWarningLabel( if (value >= 0) { result->setText(QString::number(value)); } else { + constexpr auto kMinus = QChar(0x2212); result->setMarkedText(Ui::Text::Colorized( - QString::number(value))); + kMinus + QString::number(std::abs(value)))); } result->setVisible(shown); })); @@ -223,7 +329,9 @@ Options::Option::Option( , _field( Ui::CreateChild( _content.get(), - st::createPollOptionField, + session->user()->isPremium() + ? st::createPollOptionFieldPremium + : st::createPollOptionField, Ui::InputField::Mode::NoNewlines, tr::lng_polls_create_option_add())) { InitField(outer, _field, session); @@ -299,7 +407,7 @@ void Options::Option::createRemove() { const auto remove = Ui::CreateChild( field.get(), st::createPollOptionRemove); - remove->hide(anim::type::instant); + remove->show(anim::type::instant); const auto toggle = lifetime.make_state>(false); _removeAlways = lifetime.make_state>(false); @@ -309,6 +417,7 @@ void Options::Option::createRemove() { // Don't capture 'this'! Because Option is a value type. *toggle = !field->getLastText().isEmpty(); }, field->lifetime()); +#if 0 rpl::combine( toggle->value(), _removeAlways->value(), @@ -316,6 +425,7 @@ void Options::Option::createRemove() { ) | rpl::start_with_next([=](bool shown) { remove->toggle(shown, anim::type::normal); }, remove->lifetime()); +#endif field->widthValue( ) | rpl::start_with_next([=](int width) { @@ -475,13 +585,15 @@ rpl::producer Options::Option::removeClicks() const { } Options::Options( - not_null outer, + not_null box, not_null container, - not_null session, + not_null controller, + ChatHelpers::TabbedPanel *emojiPanel, bool chooseCorrectEnabled) -: _outer(outer) +: _box(box) , _container(container) -, _session(session) +, _controller(controller) +, _emojiPanel(emojiPanel) , _chooseCorrectGroup(chooseCorrectEnabled ? createChooseCorrectGroup() : nullptr) @@ -651,12 +763,40 @@ void Options::addEmptyOption() { (*(_list.end() - 2))->toggleRemoveAlways(true); } _list.push_back(std::make_unique