From 173564bcd58671f058e8bdc08c3bff02e0fbdbd9 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 15 Jan 2021 19:18:35 +0300 Subject: [PATCH] Added initial implementation of volume menu item in group calls. --- Telegram/CMakeLists.txt | 2 + Telegram/SourceFiles/calls/calls.style | 2 + .../SourceFiles/calls/calls_group_members.cpp | 49 +++++ .../SourceFiles/calls/calls_volume_item.cpp | 170 ++++++++++++++++++ .../SourceFiles/calls/calls_volume_item.h | 67 +++++++ 5 files changed, 290 insertions(+) create mode 100644 Telegram/SourceFiles/calls/calls_volume_item.cpp create mode 100644 Telegram/SourceFiles/calls/calls_volume_item.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 72b1799cf..a71a9ac1f 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -290,6 +290,8 @@ PRIVATE calls/calls_userpic.h calls/calls_video_bubble.cpp calls/calls_video_bubble.h + calls/calls_volume_item.cpp + calls/calls_volume_item.h chat_helpers/bot_keyboard.cpp chat_helpers/bot_keyboard.h chat_helpers/emoji_keywords.cpp diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index adbf8543d..612a52b6b 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -778,6 +778,8 @@ groupCallMajorBlobMaxRadius: 4px; groupCallMinorBlobIdleRadius: 3px; groupCallMinorBlobMaxRadius: 12px; +groupCallMenuVolumeItemHeight: 100px; + callTopBarMuteCrossLine: CrossLineAnimation { fg: callBarFg; icon: icon {{ "calls/call_record_active", callBarFg }}; diff --git a/Telegram/SourceFiles/calls/calls_group_members.cpp b/Telegram/SourceFiles/calls/calls_group_members.cpp index b8364aaeb..985821650 100644 --- a/Telegram/SourceFiles/calls/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/calls_group_members.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "calls/calls_group_call.h" #include "calls/calls_group_common.h" +#include "calls/calls_volume_item.h" #include "data/data_channel.h" #include "data/data_chat.h" #include "data/data_user.h" @@ -1166,6 +1167,13 @@ base::unique_qptr MembersController::createRowContextMenu( .mute = mute, }); }); + const auto toggleMute_ = crl::guard(this, [=](bool mute, bool local) { + _toggleMuteRequests.fire(Group::MuteRequest{ + .user = user, + .mute = mute, + .locallyOnly = local, + }); + }); const auto changeVolume = crl::guard(this, [=](int volume, bool local) { _changeVolumeRequests.fire(Group::VolumeRequest{ .user = user, @@ -1252,6 +1260,47 @@ base::unique_qptr MembersController::createRowContextMenu( changeVolume(volume - 2000, false); }); } + + { + const auto call = _call.get(); + auto otherParticipantStateValue = call + ? call->otherParticipantStateValue( + ) | rpl::filter([=](const Group::ParticipantState &data) { + return data.user == user; + }) + : rpl::never(); + + auto volumeItem = base::make_unique_q( + result, + st::groupCallPopupMenu.menu, + otherParticipantStateValue, + real->volume(), + Group::kMaxVolume, + (muteState == Row::State::Muted + || muteState == Row::State::MutedByMe)); + + volumeItem->toggleMuteRequests( + ) | rpl::start_with_next([=](bool muted) { + toggleMute_(muted, false); + }, volumeItem->lifetime()); + + volumeItem->toggleMuteLocallyRequests( + ) | rpl::start_with_next([=](bool muted) { + toggleMute_(muted, true); + }, volumeItem->lifetime()); + + volumeItem->changeVolumeRequests( + ) | rpl::start_with_next([=](int volume) { + changeVolume(volume, false); + }, volumeItem->lifetime()); + + volumeItem->changeVolumeLocallyRequests( + ) | rpl::start_with_next([=](int volume) { + changeVolume(volume, true); + }, volumeItem->lifetime()); + + result->addAction(std::move(volumeItem)); + } } result->addAction( tr::lng_context_view_profile(tr::now), diff --git a/Telegram/SourceFiles/calls/calls_volume_item.cpp b/Telegram/SourceFiles/calls/calls_volume_item.cpp new file mode 100644 index 000000000..84550a48c --- /dev/null +++ b/Telegram/SourceFiles/calls/calls_volume_item.cpp @@ -0,0 +1,170 @@ +/* +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/calls_volume_item.h" + +#include "calls/calls_group_common.h" +#include "ui/widgets/continuous_sliders.h" +#include "styles/style_calls.h" +#include "styles/style_media_player.h" + +namespace Calls { +namespace { + +constexpr auto kMaxVolumePercent = 200; + +} // namespace + +MenuVolumeItem::MenuVolumeItem( + not_null parent, + const style::Menu &st, + rpl::producer participantState, + int startVolume, + int maxVolume, + bool muted) +: Ui::Menu::ItemBase(parent, st) +, _maxVolume(maxVolume) +, _cloudMuted(muted) +, _localMuted(muted) +, _slider(base::make_unique_q( + this, + st::mediaPlayerPanelPlayback)) +, _dummyAction(new QAction(parent)) +, _font(st.itemStyle.font) { + + initResizeHook(parent->sizeValue()); + enableMouseSelecting(); + + paintRequest( + ) | rpl::start_with_next([=](const QRect &clip) { + Painter p(this); + + const auto enabled = isEnabled(); + const auto selected = isSelected(); + p.fillRect(clip, selected ? st.itemBgOver : st.itemBg); + p.setPen(_localMuted + ? (selected ? st::attentionButtonFgOver : st::attentionButtonFg) + : selected + ? st.itemFgOver + : (enabled ? st.itemFg : st.itemFgDisabled)); + p.setFont(_font); + const auto volume = std::round(_slider->value() * kMaxVolumePercent); + p.drawText(QPoint(0, _font->ascent), u"%1%"_q.arg(volume)); + }, lifetime()); + + setCloudVolume(startVolume); + + _slider->setChangeProgressCallback([=](float64 value) { + const auto newMuted = (value == 0); + if (_localMuted != newMuted) { + _localMuted = newMuted; + _toggleMuteLocallyRequests.fire_copy(newMuted); + } + if (value > 0) { + _changeVolumeLocallyRequests.fire(value * _maxVolume); + } + }); + + const auto returnVolume = [=] { + _changeVolumeLocallyRequests.fire_copy(_cloudVolume); + crl::on_main(_slider.get(), [=] { + setSliderVolume(_cloudVolume); + }); + }; + + _slider->setChangeFinishedCallback([=](float64 value) { + const auto newVolume = std::round(value * _maxVolume); + const auto muted = (value == 0); + + if (!_cloudMuted && muted) { + returnVolume(); + _localMuted = true; + _toggleMuteRequests.fire(true); + } + if (_cloudMuted && muted) { + returnVolume(); + } + if (_cloudMuted && !muted) { + _waitingForUpdateVolume = true; + _localMuted = false; + _toggleMuteRequests.fire(false); + } + if (!_cloudMuted && !muted) { + _changeVolumeRequests.fire_copy(newVolume); + } + }); + + std::move( + participantState + ) | rpl::start_with_next([=](const Group::ParticipantState &state) { + const auto newMuted = state.mutedByMe; + const auto newVolume = state.volume.value_or(0); + + _cloudMuted = _localMuted = newMuted; + + if (!newVolume) { + return; + } + if (_waitingForUpdateVolume) { + const auto localVolume = + std::round(_slider->value() * _maxVolume); + if ((localVolume != newVolume) + && (_cloudVolume == newVolume)) { + _changeVolumeRequests.fire(localVolume); + } + } else { + setCloudVolume(newVolume); + } + _waitingForUpdateVolume = false; + }, lifetime()); + + _slider->setGeometry(rect()); +} + +void MenuVolumeItem::setCloudVolume(int volume) { + if (_cloudVolume == volume) { + return; + } + _cloudVolume = volume; + if (!_slider->isChanging()) { + setSliderVolume(volume); + } +} + +void MenuVolumeItem::setSliderVolume(int volume) { + _slider->setValue(float64(volume) / _maxVolume); +} + +not_null MenuVolumeItem::action() const { + return _dummyAction; +} + +bool MenuVolumeItem::isEnabled() const { + return true; +} + +int MenuVolumeItem::contentHeight() const { + return st::groupCallMenuVolumeItemHeight; +} + +rpl::producer MenuVolumeItem::toggleMuteRequests() const { + return _toggleMuteRequests.events(); +} + +rpl::producer MenuVolumeItem::toggleMuteLocallyRequests() const { + return _toggleMuteLocallyRequests.events(); +} + +rpl::producer MenuVolumeItem::changeVolumeRequests() const { + return _changeVolumeRequests.events(); +} + +rpl::producer MenuVolumeItem::changeVolumeLocallyRequests() const { + return _changeVolumeLocallyRequests.events(); +} + +} // namespace Calls diff --git a/Telegram/SourceFiles/calls/calls_volume_item.h b/Telegram/SourceFiles/calls/calls_volume_item.h new file mode 100644 index 000000000..e047a9f6f --- /dev/null +++ b/Telegram/SourceFiles/calls/calls_volume_item.h @@ -0,0 +1,67 @@ +/* +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/rp_widget.h" +#include "ui/widgets/menu/menu_item_base.h" + +namespace Ui { +class MediaSlider; +} // namespace Ui + +namespace Calls { + +namespace Group { +struct MuteRequest; +struct VolumeRequest; +struct ParticipantState; +} // namespace Group + +class MenuVolumeItem final : public Ui::Menu::ItemBase { +public: + MenuVolumeItem( + not_null parent, + const style::Menu &st, + rpl::producer participantState, + int startVolume, + int maxVolume, + bool muted); + + not_null action() const override; + bool isEnabled() const override; + + [[nodiscard]] rpl::producer toggleMuteRequests() const; + [[nodiscard]] rpl::producer toggleMuteLocallyRequests() const; + [[nodiscard]] rpl::producer changeVolumeRequests() const; + [[nodiscard]] rpl::producer changeVolumeLocallyRequests() const; + +protected: + int contentHeight() const override; + +private: + void setCloudVolume(int volume); + void setSliderVolume(int volume); + + const int _maxVolume; + int _cloudVolume = 0; + bool _waitingForUpdateVolume = false; + bool _cloudMuted = false; + bool _localMuted = false; + + const base::unique_qptr _slider; + const not_null _dummyAction; + const style::font &_font; + + rpl::event_stream _toggleMuteRequests; + rpl::event_stream _toggleMuteLocallyRequests; + rpl::event_stream _changeVolumeRequests; + rpl::event_stream _changeVolumeLocallyRequests; + +}; + +} // namespace Calls