calls_volume_item.cpp 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "calls/group/calls_volume_item.h"
  8. #include "calls/group/calls_group_common.h"
  9. #include "ui/effects/animation_value.h"
  10. #include "ui/effects/cross_line.h"
  11. #include "ui/widgets/continuous_sliders.h"
  12. #include "ui/rect.h"
  13. #include "styles/style_calls.h"
  14. #include "ui/paint/arcs.h"
  15. namespace Calls {
  16. namespace {
  17. constexpr auto kMaxVolumePercent = 200;
  18. const auto kSpeakerThreshold = std::vector<float>{
  19. 10.0f / kMaxVolumePercent,
  20. 50.0f / kMaxVolumePercent,
  21. 150.0f / kMaxVolumePercent };
  22. constexpr auto kVolumeStickedValues
  23. = std::array<std::pair<float64, float64>, 7>{{
  24. { 25. / kMaxVolumePercent, 2. / kMaxVolumePercent },
  25. { 50. / kMaxVolumePercent, 2. / kMaxVolumePercent },
  26. { 75. / kMaxVolumePercent, 2. / kMaxVolumePercent },
  27. { 100. / kMaxVolumePercent, 10. / kMaxVolumePercent },
  28. { 125. / kMaxVolumePercent, 2. / kMaxVolumePercent },
  29. { 150. / kMaxVolumePercent, 2. / kMaxVolumePercent },
  30. { 175. / kMaxVolumePercent, 2. / kMaxVolumePercent },
  31. }};
  32. } // namespace
  33. MenuVolumeItem::MenuVolumeItem(
  34. not_null<RpWidget*> parent,
  35. const style::Menu &st,
  36. const style::MediaSlider &stSlider,
  37. rpl::producer<Group::ParticipantState> participantState,
  38. int startVolume,
  39. int maxVolume,
  40. bool muted,
  41. const QMargins &padding)
  42. : Ui::Menu::ItemBase(parent, st)
  43. , _maxVolume(maxVolume)
  44. , _cloudMuted(muted)
  45. , _localMuted(muted)
  46. , _slider(base::make_unique_q<Ui::MediaSlider>(this, stSlider))
  47. , _dummyAction(new QAction(parent))
  48. , _st(st)
  49. , _stCross(st::groupCallMuteCrossLine)
  50. , _padding(padding)
  51. , _crossLineMute(std::make_unique<Ui::CrossLineAnimation>(_stCross, true))
  52. , _arcs(std::make_unique<Ui::Paint::ArcsAnimation>(
  53. st::groupCallSpeakerArcsAnimation,
  54. kSpeakerThreshold,
  55. _localMuted ? 0. : (startVolume / float(maxVolume)),
  56. Ui::Paint::ArcsAnimation::Direction::Right)) {
  57. initResizeHook(parent->sizeValue());
  58. enableMouseSelecting();
  59. enableMouseSelecting(_slider.get());
  60. _slider->setAlwaysDisplayMarker(true);
  61. sizeValue(
  62. ) | rpl::start_with_next([=](const QSize &size) {
  63. const auto geometry = QRect(QPoint(), size);
  64. _itemRect = geometry - _padding;
  65. _speakerRect = QRect(_itemRect.topLeft(), _stCross.icon.size());
  66. _arcPosition = _speakerRect.center()
  67. + QPoint(0, st::groupCallMenuSpeakerArcsSkip);
  68. _slider->setGeometry(
  69. st::groupCallMenuVolumeMargin.left(),
  70. _speakerRect.y(),
  71. (geometry.width()
  72. - st::groupCallMenuVolumeMargin.left()
  73. - st::groupCallMenuVolumeMargin.right()),
  74. _speakerRect.height());
  75. }, lifetime());
  76. setCloudVolume(startVolume);
  77. paintRequest(
  78. ) | rpl::start_with_next([=](const QRect &clip) {
  79. auto p = QPainter(this);
  80. const auto volume = _localMuted
  81. ? 0
  82. : base::SafeRound(_slider->value() * kMaxVolumePercent);
  83. const auto muteProgress
  84. = _crossLineAnimation.value((!volume) ? 1. : 0.);
  85. const auto selected = isSelected();
  86. p.fillRect(clip, selected ? st.itemBgOver : st.itemBg);
  87. const auto mutePen = anim::color(
  88. unmuteColor(),
  89. muteColor(),
  90. muteProgress);
  91. _crossLineMute->paint(
  92. p,
  93. _speakerRect.topLeft(),
  94. muteProgress,
  95. (muteProgress > 0) ? std::make_optional(mutePen) : std::nullopt);
  96. {
  97. p.translate(_arcPosition);
  98. _arcs->paint(p);
  99. }
  100. }, lifetime());
  101. _slider->setChangeProgressCallback([=](float64 value) {
  102. const auto newMuted = (value == 0);
  103. if (_localMuted != newMuted) {
  104. _localMuted = newMuted;
  105. _toggleMuteLocallyRequests.fire_copy(newMuted);
  106. _crossLineAnimation.start(
  107. [=] { update(_speakerRect); },
  108. _localMuted ? 0. : 1.,
  109. _localMuted ? 1. : 0.,
  110. st::callPanelDuration);
  111. }
  112. if (value > 0) {
  113. _changeVolumeLocallyRequests.fire(value * _maxVolume);
  114. }
  115. _arcs->setValue(value);
  116. updateSliderColor(value);
  117. });
  118. const auto returnVolume = [=] {
  119. _changeVolumeLocallyRequests.fire_copy(_cloudVolume);
  120. };
  121. _slider->setChangeFinishedCallback([=](float64 value) {
  122. const auto newVolume = base::SafeRound(value * _maxVolume);
  123. const auto muted = (value == 0);
  124. if (!_cloudMuted && muted) {
  125. returnVolume();
  126. _localMuted = true;
  127. _toggleMuteRequests.fire(true);
  128. }
  129. if (_cloudMuted && muted) {
  130. returnVolume();
  131. }
  132. if (_cloudMuted && !muted) {
  133. _waitingForUpdateVolume = true;
  134. _localMuted = false;
  135. _toggleMuteRequests.fire(false);
  136. }
  137. if (!_cloudMuted && !muted) {
  138. _changeVolumeRequests.fire_copy(newVolume);
  139. }
  140. updateSliderColor(value);
  141. });
  142. std::move(
  143. participantState
  144. ) | rpl::start_with_next([=](const Group::ParticipantState &state) {
  145. const auto newMuted = state.mutedByMe;
  146. const auto newVolume = state.volume.value_or(0);
  147. _cloudMuted = _localMuted = newMuted;
  148. if (!newVolume) {
  149. return;
  150. }
  151. if (_waitingForUpdateVolume) {
  152. const auto localVolume
  153. = base::SafeRound(_slider->value() * _maxVolume);
  154. if ((localVolume != newVolume)
  155. && (_cloudVolume == newVolume)) {
  156. _changeVolumeRequests.fire(int(localVolume));
  157. }
  158. } else {
  159. setCloudVolume(newVolume);
  160. }
  161. _waitingForUpdateVolume = false;
  162. }, lifetime());
  163. _slider->setAdjustCallback([=](float64 value) {
  164. for (const auto &snap : kVolumeStickedValues) {
  165. if (value > (snap.first - snap.second)
  166. && value < (snap.first + snap.second)) {
  167. return snap.first;
  168. }
  169. }
  170. return value;
  171. });
  172. initArcsAnimation();
  173. }
  174. void MenuVolumeItem::initArcsAnimation() {
  175. const auto lastTime = lifetime().make_state<int>(0);
  176. _arcsAnimation.init([=](crl::time now) {
  177. _arcs->update(now);
  178. update(_speakerRect);
  179. });
  180. _arcs->startUpdateRequests(
  181. ) | rpl::start_with_next([=] {
  182. if (!_arcsAnimation.animating()) {
  183. *lastTime = crl::now();
  184. _arcsAnimation.start();
  185. }
  186. }, lifetime());
  187. _arcs->stopUpdateRequests(
  188. ) | rpl::start_with_next([=] {
  189. _arcsAnimation.stop();
  190. }, lifetime());
  191. }
  192. QColor MenuVolumeItem::unmuteColor() const {
  193. return (isSelected()
  194. ? _st.itemFgOver
  195. : isEnabled()
  196. ? _st.itemFg
  197. : _st.itemFgDisabled)->c;
  198. }
  199. QColor MenuVolumeItem::muteColor() const {
  200. return (isSelected()
  201. ? st::attentionButtonFgOver
  202. : st::attentionButtonFg)->c;
  203. }
  204. void MenuVolumeItem::setCloudVolume(int volume) {
  205. if (_cloudVolume == volume) {
  206. return;
  207. }
  208. _cloudVolume = volume;
  209. if (!_slider->isChanging()) {
  210. setSliderVolume(_cloudMuted ? 0. : volume);
  211. }
  212. }
  213. void MenuVolumeItem::setSliderVolume(int volume) {
  214. const auto value = float64(volume) / _maxVolume;
  215. _slider->setValue(value);
  216. updateSliderColor(value);
  217. }
  218. void MenuVolumeItem::updateSliderColor(float64 value) {
  219. value = std::clamp(value, 0., 1.);
  220. const auto color = [](int rgb) {
  221. return QColor(
  222. int((rgb & 0xFF0000) >> 16),
  223. int((rgb & 0x00FF00) >> 8),
  224. int(rgb & 0x0000FF));
  225. };
  226. const auto colors = std::array<QColor, 4>{ {
  227. color(0xF66464),
  228. color(0xD0B738),
  229. color(0x24CD80),
  230. color(0x3BBCEC),
  231. } };
  232. _slider->setActiveFgOverride((value < 0.25)
  233. ? anim::color(colors[0], colors[1], value / 0.25)
  234. : (value < 0.5)
  235. ? anim::color(colors[1], colors[2], (value - 0.25) / 0.25)
  236. : anim::color(colors[2], colors[3], (value - 0.5) / 0.5));
  237. }
  238. not_null<QAction*> MenuVolumeItem::action() const {
  239. return _dummyAction;
  240. }
  241. bool MenuVolumeItem::isEnabled() const {
  242. return true;
  243. }
  244. int MenuVolumeItem::contentHeight() const {
  245. return rect::m::sum::v(_padding) + _stCross.icon.height();
  246. }
  247. rpl::producer<bool> MenuVolumeItem::toggleMuteRequests() const {
  248. return _toggleMuteRequests.events();
  249. }
  250. rpl::producer<bool> MenuVolumeItem::toggleMuteLocallyRequests() const {
  251. return _toggleMuteLocallyRequests.events();
  252. }
  253. rpl::producer<int> MenuVolumeItem::changeVolumeRequests() const {
  254. return _changeVolumeRequests.events();
  255. }
  256. rpl::producer<int> MenuVolumeItem::changeVolumeLocallyRequests() const {
  257. return _changeVolumeLocallyRequests.events();
  258. }
  259. } // namespace Calls