mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-02-24 01:06:59 +00:00
Added ability to insert custom emoji to polls.
This commit is contained in:
parent
e6c22ec1ca
commit
c803603de4
@ -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) {
|
||||
|
@ -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<Ui::EmojiButton*> AddEmojiToggleToField(
|
||||
not_null<Ui::InputField*> field,
|
||||
not_null<Ui::BoxContent*> box,
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<ChatHelpers::TabbedPanel*> emojiPanel,
|
||||
QPoint shift) {
|
||||
const auto emojiToggle = Ui::CreateChild<Ui::EmojiButton>(
|
||||
field->parentWidget(),
|
||||
st::defaultComposeFiles.emoji);
|
||||
const auto fade = Ui::CreateChild<Ui::FadeAnimation>(
|
||||
emojiToggle,
|
||||
emojiToggle,
|
||||
0.5);
|
||||
{
|
||||
const auto fadeTarget = Ui::CreateChild<Ui::RpWidget>(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<DocumentData*>) { 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<QEvent*> 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<QWidget*> outer,
|
||||
not_null<Ui::BoxContent*> box,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
not_null<Main::Session*> session,
|
||||
not_null<Window::SessionController*> controller,
|
||||
ChatHelpers::TabbedPanel *emojiPanel,
|
||||
bool chooseCorrectEnabled);
|
||||
|
||||
[[nodiscard]] bool hasOptions() const;
|
||||
@ -140,9 +243,10 @@ private:
|
||||
[[nodiscard]] auto createChooseCorrectGroup()
|
||||
-> std::shared_ptr<Ui::RadiobuttonGroup>;
|
||||
|
||||
not_null<QWidget*> _outer;
|
||||
not_null<Ui::BoxContent*> _box;
|
||||
not_null<Ui::VerticalLayout*> _container;
|
||||
const not_null<Main::Session*> _session;
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
ChatHelpers::TabbedPanel * const _emojiPanel;
|
||||
std::shared_ptr<Ui::RadiobuttonGroup> _chooseCorrectGroup;
|
||||
int _position = 0;
|
||||
std::vector<std::unique_ptr<Option>> _list;
|
||||
@ -154,6 +258,7 @@ private:
|
||||
rpl::event_stream<not_null<QWidget*>> _scrollToWidget;
|
||||
rpl::event_stream<> _backspaceInFront;
|
||||
rpl::event_stream<> _tabbed;
|
||||
rpl::lifetime _emojiPanelLifetime;
|
||||
|
||||
};
|
||||
|
||||
@ -193,8 +298,9 @@ not_null<Ui::FlatLabel*> 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<Ui::InputField>(
|
||||
_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<Ui::CrossButton>(
|
||||
field.get(),
|
||||
st::createPollOptionRemove);
|
||||
remove->hide(anim::type::instant);
|
||||
remove->show(anim::type::instant);
|
||||
|
||||
const auto toggle = lifetime.make_state<rpl::variable<bool>>(false);
|
||||
_removeAlways = lifetime.make_state<rpl::variable<bool>>(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<Qt::MouseButton> Options::Option::removeClicks() const {
|
||||
}
|
||||
|
||||
Options::Options(
|
||||
not_null<QWidget*> outer,
|
||||
not_null<Ui::BoxContent*> box,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
not_null<Main::Session*> session,
|
||||
not_null<Window::SessionController*> 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<Option>(
|
||||
_outer,
|
||||
_box,
|
||||
_container,
|
||||
_session,
|
||||
&_controller->session(),
|
||||
_position + _list.size() + _destroyed.size(),
|
||||
_chooseCorrectGroup));
|
||||
const auto field = _list.back()->field();
|
||||
if (const auto emojiPanel = _emojiPanel) {
|
||||
const auto emojiToggle = AddEmojiToggleToField(
|
||||
field,
|
||||
_box,
|
||||
_controller,
|
||||
emojiPanel,
|
||||
QPoint(
|
||||
-st::createPollOptionFieldPremium.textMargins.right(),
|
||||
st::createPollOptionEmojiPositionSkip));
|
||||
emojiToggle->shownValue() | rpl::start_with_next([=](bool shown) {
|
||||
if (!shown) {
|
||||
return;
|
||||
}
|
||||
_emojiPanelLifetime.destroy();
|
||||
emojiPanel->selector()->emojiChosen(
|
||||
) | rpl::start_with_next([=](ChatHelpers::EmojiChosen data) {
|
||||
if (field->hasFocus()) {
|
||||
Ui::InsertEmojiAtCursor(field->textCursor(), data.emoji);
|
||||
}
|
||||
}, _emojiPanelLifetime);
|
||||
emojiPanel->selector()->customEmojiChosen(
|
||||
) | rpl::start_with_next([=](ChatHelpers::FileChosen data) {
|
||||
if (field->hasFocus()) {
|
||||
Data::InsertCustomEmoji(field, data.document);
|
||||
}
|
||||
}, _emojiPanelLifetime);
|
||||
}, emojiToggle->lifetime());
|
||||
}
|
||||
field->submits(
|
||||
) | rpl::start_with_next([=] {
|
||||
const auto index = findField(field);
|
||||
@ -703,7 +843,7 @@ void Options::addEmptyOption() {
|
||||
});
|
||||
|
||||
_list.back()->removeClicks(
|
||||
) | rpl::take(1) | rpl::start_with_next([=] {
|
||||
) | rpl::start_with_next([=] {
|
||||
Ui::PostponeCall(crl::guard(field, [=] {
|
||||
Expects(!_list.empty());
|
||||
|
||||
@ -795,19 +935,63 @@ not_null<Ui::InputField*> CreatePollBox::setupQuestion(
|
||||
using namespace Settings;
|
||||
|
||||
const auto session = &_controller->session();
|
||||
const auto isPremium = session->user()->isPremium();
|
||||
Ui::AddSubsectionTitle(container, tr::lng_polls_create_question());
|
||||
|
||||
const auto question = container->add(
|
||||
object_ptr<Ui::InputField>(
|
||||
container,
|
||||
st::createPollField,
|
||||
Ui::InputField::Mode::MultiLine,
|
||||
tr::lng_polls_create_question_placeholder()),
|
||||
st::createPollFieldPadding);
|
||||
st::createPollFieldPadding
|
||||
+ (isPremium
|
||||
? QMargins(0, 0, st::defaultComposeFiles.emoji.inner.width, 0)
|
||||
: QMargins()));
|
||||
InitField(getDelegate()->outerContainer(), question, session);
|
||||
question->setMaxLength(kQuestionLimit + kErrorLimit);
|
||||
question->setSubmitSettings(Ui::InputField::SubmitSettings::Both);
|
||||
question->customTab(true);
|
||||
|
||||
if (isPremium) {
|
||||
using Selector = ChatHelpers::TabbedSelector;
|
||||
const auto outer = getDelegate()->outerContainer();
|
||||
_emojiPanel = base::make_unique_q<ChatHelpers::TabbedPanel>(
|
||||
outer,
|
||||
_controller,
|
||||
object_ptr<Selector>(
|
||||
nullptr,
|
||||
_controller->uiShow(),
|
||||
Window::GifPauseReason::Layer,
|
||||
Selector::Mode::EmojiOnly));
|
||||
const auto emojiPanel = _emojiPanel.get();
|
||||
emojiPanel->setDesiredHeightValues(
|
||||
1.,
|
||||
st::emojiPanMinHeight / 2,
|
||||
st::emojiPanMinHeight);
|
||||
emojiPanel->hide();
|
||||
emojiPanel->selector()->setCurrentPeer(session->user());
|
||||
|
||||
const auto emojiToggle = AddEmojiToggleToField(
|
||||
question,
|
||||
this,
|
||||
_controller,
|
||||
emojiPanel,
|
||||
st::createPollOptionFieldPremiumEmojiPosition);
|
||||
emojiPanel->selector()->emojiChosen(
|
||||
) | rpl::start_with_next([=](ChatHelpers::EmojiChosen data) {
|
||||
if (question->hasFocus()) {
|
||||
Ui::InsertEmojiAtCursor(question->textCursor(), data.emoji);
|
||||
}
|
||||
}, emojiToggle->lifetime());
|
||||
emojiPanel->selector()->customEmojiChosen(
|
||||
) | rpl::start_with_next([=](ChatHelpers::FileChosen data) {
|
||||
if (question->hasFocus()) {
|
||||
Data::InsertCustomEmoji(question, data.document);
|
||||
}
|
||||
}, emojiToggle->lifetime());
|
||||
}
|
||||
|
||||
const auto warning = CreateWarningLabel(
|
||||
container,
|
||||
question,
|
||||
@ -916,9 +1100,10 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
|
||||
st::defaultSubsectionTitle),
|
||||
st::createPollFieldTitlePadding);
|
||||
const auto options = lifetime().make_state<Options>(
|
||||
getDelegate()->outerContainer(),
|
||||
this,
|
||||
container,
|
||||
&_controller->session(),
|
||||
_controller,
|
||||
_emojiPanel ? _emojiPanel.get() : nullptr,
|
||||
(_chosen & PollData::Flag::Quiz));
|
||||
auto limit = options->usedCount() | rpl::after_next([=](int count) {
|
||||
setCloseByEscape(!count);
|
||||
|
@ -14,6 +14,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
struct PollData;
|
||||
|
||||
namespace ChatHelpers {
|
||||
class TabbedPanel;
|
||||
} // namespace ChatHelpers
|
||||
|
||||
namespace Ui {
|
||||
class VerticalLayout;
|
||||
} // namespace Ui
|
||||
@ -72,6 +76,7 @@ private:
|
||||
const PollData::Flags _disabled = PollData::Flags();
|
||||
const Api::SendType _sendType = Api::SendType();
|
||||
const SendMenu::Type _sendMenuType;
|
||||
base::unique_qptr<ChatHelpers::TabbedPanel> _emojiPanel;
|
||||
Fn<void()> _setInnerFocus;
|
||||
Fn<rpl::producer<bool>()> _dataIsValidValue;
|
||||
rpl::event_stream<Result> _submitRequests;
|
||||
|
Loading…
Reference in New Issue
Block a user