diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index ac8cad7fe9..76c6cc1161 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -363,6 +363,8 @@ PRIVATE calls/group/calls_group_members_row.h calls/group/calls_group_menu.cpp calls/group/calls_group_menu.h + calls/group/calls_group_message_field.cpp + calls/group/calls_group_message_field.h calls/group/calls_group_panel.cpp calls/group/calls_group_panel.h calls/group/calls_group_rtmp.cpp diff --git a/Telegram/SourceFiles/calls/group/calls_group_message_field.cpp b/Telegram/SourceFiles/calls/group/calls_group_message_field.cpp new file mode 100644 index 0000000000..3dfcb4da86 --- /dev/null +++ b/Telegram/SourceFiles/calls/group/calls_group_message_field.cpp @@ -0,0 +1,157 @@ +/* +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 +*/ +#include "calls/group/calls_group_message_field.h" + +#include "chat_helpers/compose/compose_show.h" +#include "lang/lang_keys.h" +#include "ui/controls/emoji_button.h" +#include "ui/controls/send_button.h" +#include "ui/widgets/fields/input_field.h" +#include "ui/painter.h" +#include "ui/ui_utility.h" +#include "styles/style_chat_helpers.h" +#include "styles/style_chat.h" +#include "styles/style_media_view.h" + +namespace Calls::Group { + +MessageField::MessageField( + not_null parent, + std::shared_ptr show) +: _parent(parent) +, _show(std::move(show)) +, _wrap(std::make_unique(_parent)) { + createControls(); +} + +void MessageField::createControls() { + setupBackground(); + + const auto &st = st::storiesComposeControls; + _field = Ui::CreateChild( + _wrap.get(), + st.field, + Ui::InputField::Mode::MultiLine, + tr::lng_message_ph()); + _field->setMinHeight( + st::historySendSize.height() - 2 * st::historySendPadding); + _field->setMaxHeight(st::historyComposeFieldMaxHeight); + _field->setDocumentMargin(4.); + _field->setAdditionalMargin(style::ConvertScale(4) - 4); + + _send = Ui::CreateChild(_wrap.get(), st.send); + + _emojiToggle = Ui::CreateChild(_wrap.get(), st.emoji); + + _width.value( + ) | rpl::filter( + rpl::mappers::_1 > 0 + ) | rpl::start_with_next([=](int newWidth) { + _wrap->resizeToWidth(newWidth); + + const auto fieldWidth = newWidth + - st::historySendPadding + - _emojiToggle->width() + - _send->width(); + _field->resizeToWidth(fieldWidth); + _field->moveToLeft( + st::historySendPadding, + st::historySendPadding, + newWidth); + _send->moveToRight(0, 0); + _emojiToggle->moveToRight(_send->width(), 0); + }, _lifetime); + + _field->heightValue() | rpl::start_with_next([=](int height) { + _wrap->resize(_wrap->width(), height + 2 * st::historySendPadding); + }, _lifetime); + + rpl::merge( + _field->submits() | rpl::to_empty, + _send->clicks() | rpl::to_empty + ) | rpl::start_with_next([=] { + _submitted.fire(_field->getTextWithAppliedMarkdown()); + }, _lifetime); +} + +void MessageField::setupBackground() { + _wrap->paintRequest() | rpl::start_with_next([=] { + const auto radius = st::historySendSize.height() / 2.; + auto p = QPainter(_wrap.get()); + auto hq = PainterHighQualityEnabler(p); + + p.setPen(Qt::NoPen); + p.setBrush(st::storiesComposeBg); + p.drawRoundedRect(_wrap->rect(), radius, radius); + }, _lifetime); +} + +void MessageField::resizeToWidth(int newWidth) { + _width = newWidth; + if (_wrap->isHidden()) { + Ui::SendPendingMoveResizeEvents(_wrap.get()); + } +} + +void MessageField::move(int x, int y) { + _wrap->move(x, y); +} + +void MessageField::toggle(bool shown) { + if (_shown == shown) { + return; + } else if (shown) { + Assert(_width.current() > 0); + Ui::SendPendingMoveResizeEvents(_wrap.get()); + } + _shown = shown; + shownAnimationCallback(); + //_shownAnimation.start( + // [=] { shownAnimationCallback(); }, + // shown ? 0. : 1., + // shown ? 1. : 0., + // st::slideWrapDuration, + // anim::easeOutCirc); +} + +void MessageField::raise() { + _wrap->raise(); +} + +void MessageField::shownAnimationCallback() { + if (_shownAnimation.animating()) { + _wrap->update(); + } else if (_shown) { + _wrap->show(); + _field->setFocusFast(); + } else { + _closed.fire({}); + } +} + +int MessageField::height() const { + return _shownAnimation.value(_shown ? 1. : 0.) * _wrap->height(); +} + +rpl::producer MessageField::heightValue() const { + return _wrap->heightValue(); +} + +rpl::producer MessageField::submitted() const { + return _submitted.events(); +} + +rpl::producer<> MessageField::closed() const { + return _closed.events(); +} + +rpl::lifetime &MessageField::lifetime() { + return _lifetime; +} + +} // namespace Calls::Group diff --git a/Telegram/SourceFiles/calls/group/calls_group_message_field.h b/Telegram/SourceFiles/calls/group/calls_group_message_field.h new file mode 100644 index 0000000000..b573ed561b --- /dev/null +++ b/Telegram/SourceFiles/calls/group/calls_group_message_field.h @@ -0,0 +1,71 @@ +/* +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" + +struct TextWithTags; + +namespace ChatHelpers { +class Show; +} // namespace ChatHelpers + +namespace Ui { +class EmojiButton; +class InputField; +class SendButton; +class RpWidget; +} // namespace Ui + +namespace Calls::Group { + +class MessageField final { +public: + MessageField( + not_null parent, + std::shared_ptr show); + + void resizeToWidth(int newWidth); + void move(int x, int y); + void toggle(bool shown); + void raise(); + + [[nodiscard]] int height() const; + [[nodiscard]] rpl::producer heightValue() const; + + [[nodiscard]] rpl::producer submitted() const; + [[nodiscard]] rpl::producer<> closed() const; + + [[nodiscard]] rpl::lifetime &lifetime(); + +private: + void createControls(); + void setupBackground(); + void shownAnimationCallback(); + + const not_null _parent; + const std::shared_ptr _show; + const std::unique_ptr _wrap; + + Ui::InputField *_field = nullptr; + Ui::SendButton *_send = nullptr; + Ui::EmojiButton *_emojiToggle = nullptr; + + rpl::variable _width; + + bool _shown = false; + Ui::Animations::Simple _shownAnimation; + + rpl::event_stream _submitted; + rpl::event_stream<> _closed; + + rpl::lifetime _lifetime; + +}; + +} // namespace Calls::Group diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp index 631ade0a39..484033c37f 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp @@ -8,16 +8,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "calls/group/calls_group_panel.h" #include "calls/group/calls_group_common.h" -#include "calls/group/calls_group_members.h" -#include "calls/group/calls_group_settings.h" -#include "calls/group/calls_group_menu.h" -#include "calls/group/calls_group_viewport.h" -#include "calls/group/calls_group_toasts.h" #include "calls/group/calls_group_invite_controller.h" +#include "calls/group/calls_group_members.h" +#include "calls/group/calls_group_menu.h" +#include "calls/group/calls_group_message_field.h" +#include "calls/group/calls_group_settings.h" +#include "calls/group/calls_group_toasts.h" +#include "calls/group/calls_group_viewport.h" #include "calls/group/ui/calls_group_scheduled_labels.h" #include "calls/group/ui/desktop_capture_choose_source.h" #include "calls/calls_emoji_fingerprint.h" #include "calls/calls_window.h" +#include "chat_helpers/compose/compose_show.h" +#include "data/data_file_origin.h" #include "ui/platform/ui_platform_window_title.h" // TitleLayout #include "ui/platform/ui_platform_utility.h" #include "ui/controls/call_mute_button.h" @@ -50,6 +53,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/session/session_show.h" #include "main/main_app_config.h" #include "main/main_session.h" +#include "menu/menu_send.h" #include "base/event_filter.h" #include "base/unixtime.h" #include "base/qt_signal_producer.h" @@ -94,6 +98,96 @@ void UnpinMaximized(not_null widget) { } #endif // Q_OS_WIN +class Show final : public ChatHelpers::Show { +public: + Show(not_null panel, std::shared_ptr base) + : _panel(panel) + , _base(std::move(base)) { + } + + void activate() override { + if (const auto panel = _panel.get()) { + if (!panel->window()->isHidden()) { + panel->window()->activateWindow(); + } + } + } + + void showOrHideBoxOrLayer( + std::variant< + v::null_t, + object_ptr, + std::unique_ptr> &&layer, + Ui::LayerOptions options, + anim::type animated) const override { + _base->showOrHideBoxOrLayer( + std::move(layer), + options, + anim::type::normal); + } + not_null toastParent() const override { + return _base->toastParent(); + } + bool valid() const override { + return _panel.get() != nullptr; + } + operator bool() const override { + return valid(); + } + + Main::Session &session() const override { + const auto panel = _panel.get(); + Assert(panel != nullptr); + + return panel->call()->peer()->session(); + } + bool paused(ChatHelpers::PauseReason reason) const override { + const auto panel = _panel.get(); + if (!panel) { + return false; + } else if (panel->window()->isHidden() + || (!panel->window()->isFullScreen() + && !panel->window()->isActiveWindow())) { + return true; + } else if (reason < ChatHelpers::PauseReason::Layer + && panel->callWindow()->topShownLayer() != nullptr) { + return true; + } + return false; + } + rpl::producer<> pauseChanged() const override { + return rpl::never<>(); + } + + rpl::producer adjustShadowLeft() const override { + return rpl::single(false); + } + SendMenu::Details sendMenuDetails() const override { + return { SendMenu::Type::Disabled }; + } + + bool showMediaPreview( + Data::FileOrigin origin, + not_null document) const override { + return false; // #TODO stories + } + bool showMediaPreview( + Data::FileOrigin origin, + not_null photo) const override { + return false; // #TODO stories + } + + void processChosenSticker( + ChatHelpers::FileChosen &&chosen) const override { + //_panel->emojiChosen(std::move(chosen)); + } + +private: + const base::weak_ptr _panel; + const std::shared_ptr _base; + +}; + } // namespace struct Panel::ControlsBackgroundNarrow { @@ -104,6 +198,7 @@ struct Panel::ControlsBackgroundNarrow { Ui::RpWidget shadow; Ui::RpWidget blocker; + int shadowHeight = 0; }; Panel::Panel(not_null call) @@ -191,12 +286,11 @@ bool Panel::isActive() const { return window()->isActiveWindow() && isVisible(); } -std::shared_ptr Panel::sessionShow() { - return Main::MakeSessionShow(uiShow(), &_peer->session()); -} - -std::shared_ptr Panel::uiShow() { - return _window->uiShow(); +std::shared_ptr Panel::uiShow() { + if (!_cachedShow) { + _cachedShow = std::make_shared(this, _window->uiShow()); + } + return _cachedShow; } void Panel::minimize() { @@ -387,6 +481,37 @@ void Panel::initWidget() { }, lifetime()); } +void Panel::toggleMessageTyping() { + const auto typing = !_messageTyping.current(); + if (_messageField) { + _messageField->toggle(typing); + } else if (typing) { + _messageField = std::make_unique(widget(), uiShow()); + + updateButtonsGeometry(); + _messageField->toggle(true); + + _messageField->submitted( + ) | rpl::start_with_next([=](TextWithTags text) { + //_call->sendMessage(text); + _messageField->toggle(false); + _messageTyping = false; + updateWideControlsVisibility(); + }, _messageField->lifetime()); + + _messageField->heightValue() | rpl::start_with_next([=] { + updateButtonsGeometry(); + }, _messageField->lifetime()); + + _messageField->closed() | rpl::start_with_next([=] { + _messageField = nullptr; + updateButtonsGeometry(); + }, _messageField->lifetime()); + } + _messageTyping = typing; + updateWideControlsVisibility(); +} + void Panel::endCall() { if (!_call->canManage()) { _call->hangup(); @@ -643,6 +768,7 @@ void Panel::refreshVideoButtons(std::optional overrideWideMode) { st::groupCallMessageSmall, &st::groupCallMessageActiveSmall); _message->show(); + _message->setClickedCallback([=] { toggleMessageTyping(); }); _message->setColorOverrides( toggleableOverrides(_messageTyping.value())); } @@ -922,7 +1048,7 @@ Fn Panel::shareConferenceLinkCallback() { return [=] { Expects(_call->conference()); - ShowConferenceCallLinkBox(sessionShow(), _call->conferenceCall(), { + ShowConferenceCallLinkBox(uiShow(), _call->conferenceCall(), { .st = DarkConferenceCallLinkStyle(), }); }; @@ -930,7 +1056,7 @@ Fn Panel::shareConferenceLinkCallback() { void Panel::migrationShowShareLink() { ShowConferenceCallLinkBox( - sessionShow(), + uiShow(), _call->conferenceCall(), { .st = DarkConferenceCallLinkStyle() }); } @@ -1025,6 +1151,9 @@ void Panel::raiseControls() { if (_pinOnTop) { _pinOnTop->raise(); } + if (_messageField) { + _messageField->raise(); + } _window->raiseLayers(); if (_niceTooltip) { _niceTooltip->raise(); @@ -1110,7 +1239,8 @@ void Panel::toggleWideControls(bool shown) { void Panel::updateWideControlsVisibility() { const auto shown = _showWideControls - || (_stickedTooltipClose != nullptr); + || (_stickedTooltipClose != nullptr) + || _messageTyping.current(); if (_wideControlsShown == shown) { return; } @@ -1845,6 +1975,7 @@ void Panel::setupControlsBackgroundNarrow() { const auto height = std::max( st::groupCallMembersShadowHeight, st::groupCallMembersFadeSkip + st::groupCallMembersFadeHeight); + _controlsBackgroundNarrow->shadowHeight = height; const auto full = lifetime.make_state( QSize(1, height * factor), QImage::Format_ARGB32_Premultiplied); @@ -1897,7 +2028,9 @@ void Panel::setupControlsBackgroundNarrow() { const auto inner = _members->getInnerGeometry().translated( _members->x() - _controlsBackgroundNarrow->shadow.x(), _members->y() - _controlsBackgroundNarrow->shadow.y()); - const auto faded = clip.intersected(inner); + const auto bottom = _controlsBackgroundNarrow->shadowHeight; + const auto faded = clip.intersected(inner).intersected( + QRect(clip.x(), 0, clip.width(), bottom)); if (!faded.isEmpty()) { const auto factor = style::DevicePixelRatio(); p.drawImage( @@ -1909,9 +2042,8 @@ void Panel::setupControlsBackgroundNarrow() { full->width(), faded.height() * factor)); } - const auto bottom = inner.y() + inner.height(); const auto after = clip.intersected(QRect( - 0, + inner.x(), bottom, inner.width(), _controlsBackgroundNarrow->shadow.height() - bottom)); @@ -2239,6 +2371,7 @@ void Panel::updateButtonsGeometry() { widget->setVisible(shown); } }; + auto messagesBottomSkip = 0; if (mode() == PanelMode::Wide) { Assert(_video != nullptr); Assert(_message != nullptr); @@ -2256,10 +2389,6 @@ void Panel::updateButtonsGeometry() { _viewport->setControlsShown(rtmp ? 0. : shown); } - const auto buttonsTop = widget()->height() - anim::interpolate( - 0, - st::groupCallButtonBottomSkipWide, - shown); const auto addSkip = st::callMuteButtonSmall.active.outerRadius; const auto muteSize = _mute->innerSize().width() + 2 * addSkip; const auto skip = st::groupCallButtonSkipSmall; @@ -2276,6 +2405,28 @@ void Panel::updateButtonsGeometry() { - membersWidth - membersSkip - fullWidth) / 2; + + if (_messageField) { + const auto paddedWidth = fullWidth + + st::groupCallControlsBackMargin.left() + + st::groupCallControlsBackMargin.right(); + _messageField->resizeToWidth(paddedWidth); + messagesBottomSkip += _messageField->height(); + + const auto existing = st::groupCallButtonBottomSkipWide + - _hangup->height() + - st::groupCallControlsBackMargin.bottom(); + const auto x = left - st::groupCallControlsBackMargin.left(); + const auto y = widget()->height() + - messagesBottomSkip + - (existing / 2); + _messageField->move(x, y); + } + + const auto buttonsTop = widget()->height() + - messagesBottomSkip + - anim::interpolate(0, st::groupCallButtonBottomSkipWide, shown); + //toggle(_screenShare, !hidden && !rtmp); toggle(_message, !hidden && !rtmp); //if (!rtmp) { @@ -2326,16 +2477,33 @@ void Panel::updateButtonsGeometry() { refreshTitleGeometry(); } } else { - const auto muteTop = widget()->height() - - st::groupCallMuteBottomSkip; - const auto buttonsTop = widget()->height() - - st::groupCallButtonBottomSkip; const auto muteSize = _mute->innerSize().width(); const auto single = (_settings ? _settings : _callShare)->width(); const auto five = !_callShare && _message; const auto fullWidth = five ? st::groupCallWidth : (muteSize + 2 * (single + st::groupCallButtonSkip)); + if (_messageField) { + const auto paddedWidth = st::groupCallWidth + - st::groupCallMembersMargin.left() + - st::groupCallMembersMargin.right(); + _messageField->resizeToWidth(paddedWidth); + + const auto existing = st::groupCallButtonBottomSkip + - _mute->innerSize().height(); + const auto height = _messageField->height(); + messagesBottomSkip += height + std::min(height, existing / 3); + + const auto x = (widget()->width() - paddedWidth) / 2; + const auto y = widget()->height() - messagesBottomSkip; + _messageField->move(x, y); + } + const auto muteTop = widget()->height() + - messagesBottomSkip + - st::groupCallMuteBottomSkip; + const auto buttonsTop = widget()->height() + - messagesBottomSkip + - st::groupCallButtonBottomSkip; toggle(_mute, true); _mute->moveInner({ (widget()->width() - muteSize) / 2, muteTop }); const auto leftButtonLeft = (widget()->width() - fullWidth) / 2; @@ -2372,17 +2540,19 @@ void Panel::updateButtonsGeometry() { _controlsBackgroundNarrow->shadow.setGeometry( left, (widget()->height() + - messagesBottomSkip - st::groupCallMembersMargin.bottom() - - _controlsBackgroundNarrow->shadow.height()), + - _controlsBackgroundNarrow->shadowHeight), width, - _controlsBackgroundNarrow->shadow.height()); + messagesBottomSkip + _controlsBackgroundNarrow->shadowHeight); _controlsBackgroundNarrow->blocker.setGeometry( left, (widget()->height() + - messagesBottomSkip - st::groupCallMembersMargin.bottom() - st::groupCallMembersBottomSkip), width, - st::groupCallMembersBottomSkip); + messagesBottomSkip + st::groupCallMembersBottomSkip); } updateTooltipGeometry(); } @@ -2659,6 +2829,10 @@ bool Panel::handleClose() { return false; } +not_null Panel::callWindow() const { + return _window.get(); +} + not_null Panel::window() const { return _window->window(); } diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.h b/Telegram/SourceFiles/calls/group/calls_group_panel.h index c0109df006..962b086706 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.h +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.h @@ -16,21 +16,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class Image; +namespace ChatHelpers { +class Show; +} // namespace ChatHelpers + namespace Data { class PhotoMedia; class GroupCall; } // namespace Data -namespace Main { -class SessionShow; -} // namespace Main - namespace Ui { class Show; class BoxContent; -class LayerWidget; -enum class LayerOption; -using LayerOptions = base::flags; class AbstractButton; class ImportantTooltip; class DropdownMenu; @@ -73,6 +70,7 @@ class Viewport; enum class PanelMode; enum class StickedTooltip; class MicLevelTester; +class MessageField; class Panel final : public base::has_weak_ptr @@ -97,9 +95,8 @@ public: void showAndActivate(); void closeBeforeDestroy(); - [[nodiscard]] std::shared_ptr sessionShow(); - [[nodiscard]] std::shared_ptr uiShow(); - + [[nodiscard]] std::shared_ptr uiShow(); + [[nodiscard]] not_null callWindow() const; [[nodiscard]] not_null window() const; rpl::lifetime &lifetime(); @@ -171,6 +168,7 @@ private: void updateWideControlsVisibility(); [[nodiscard]] bool videoButtonInNarrowMode() const; [[nodiscard]] Fn shareConferenceLinkCallback(); + void toggleMessageTyping(); void endCall(); @@ -258,6 +256,9 @@ private: StickedTooltips _stickedTooltipsShown; Fn _callShareLinkCallback; + std::shared_ptr _cachedShow; + std::unique_ptr _messageField; + const std::unique_ptr _toasts; std::unique_ptr _micLevelTester; diff --git a/Telegram/SourceFiles/calls/group/calls_group_toasts.cpp b/Telegram/SourceFiles/calls/group/calls_group_toasts.cpp index 584a30b551..67b6167805 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_toasts.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_toasts.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "calls/group/calls_group_call.h" #include "calls/group/calls_group_common.h" #include "calls/group/calls_group_panel.h" +#include "chat_helpers/compose/compose_show.h" #include "data/data_peer.h" #include "data/data_group_call.h" #include "ui/layers/show.h"