diff --git a/Telegram/Resources/icons/calls/voice_mute_mini.png b/Telegram/Resources/icons/calls/voice_mute_mini.png new file mode 100644 index 000000000..75615e4b0 Binary files /dev/null and b/Telegram/Resources/icons/calls/voice_mute_mini.png differ diff --git a/Telegram/Resources/icons/calls/voice_mute_mini@2x.png b/Telegram/Resources/icons/calls/voice_mute_mini@2x.png new file mode 100644 index 000000000..9eec8a1b3 Binary files /dev/null and b/Telegram/Resources/icons/calls/voice_mute_mini@2x.png differ diff --git a/Telegram/Resources/icons/calls/voice_mute_mini@3x.png b/Telegram/Resources/icons/calls/voice_mute_mini@3x.png new file mode 100644 index 000000000..668b4a92b Binary files /dev/null and b/Telegram/Resources/icons/calls/voice_mute_mini@3x.png differ diff --git a/Telegram/SourceFiles/boxes/peer_list_box.h b/Telegram/SourceFiles/boxes/peer_list_box.h index abce0b55b..44b52d5ec 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.h +++ b/Telegram/SourceFiles/boxes/peer_list_box.h @@ -122,7 +122,7 @@ public: bool actionSelected) { } - void refreshName(const style::PeerListItem &st); + virtual void refreshName(const style::PeerListItem &st); const Ui::Text::String &name() const { return _name; } diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index 0344576f4..c5773c259 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -1134,6 +1134,10 @@ desktopCaptureSubmit: RoundButton(desktopCaptureCancel) { groupCallNarrowSkip: 9px; groupCallNarrowRowSkip: 8px; groupCallNarrowSize: size(144px, 90px); +groupCallNarrowUserpicTop: 12px; +groupCallNarrowNameTop: 65px; +groupCallNarrowIconTop: 62px; +groupCallNarrowIconLess: 5px; groupCallWideModeWidthMin: 520px; groupCallWideModeSize: size(720px, 480px); groupCallNarrowAddMemberHeight: 32px; @@ -1149,3 +1153,19 @@ groupCallNarrowAddMember: RoundButton(defaultActiveButton) { ripple: groupCallRipple; } +groupCallNarrowInactiveCrossLine: CrossLineAnimation { + fg: groupCallMemberInactiveIcon; + icon: icon {{ "calls/voice_mute_mini", groupCallMemberInactiveIcon }}; + startPosition: point(7px, 4px); + endPosition: point(17px, 15px); + stroke: 3px; + strokeDenominator: 2; +} +groupCallNarrowColoredCrossLine: CrossLineAnimation(groupCallNarrowInactiveCrossLine) { + fg: groupCallMemberMutedIcon; + icon: icon {{ "calls/voice_mute_mini", groupCallMemberActiveIcon }}; +} +groupCallVideoCrossLine: CrossLineAnimation(groupCallNarrowInactiveCrossLine) { + fg: groupCallVideoTextFg; + icon: icon {{ "calls/voice_mute_mini", groupCallVideoTextFg }}; +} diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.cpp b/Telegram/SourceFiles/calls/group/calls_group_members.cpp index 215ca29d4..140085902 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members.cpp @@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/scroll_area.h" #include "ui/widgets/popup_menu.h" #include "ui/text/text_utilities.h" +#include "ui/text/text_options.h" #include "ui/effects/ripple_animation.h" #include "ui/effects/cross_line.h" #include "ui/round_rect.h" @@ -82,6 +83,12 @@ auto RowBlobs() -> std::array { class Row; +enum class NarrowStyle { + None, + Userpic, + Video, +}; + class RowDelegate { public: struct IconState { @@ -90,6 +97,7 @@ public: float64 muted = 0.; bool mutedByMe = false; bool raisedHand = false; + NarrowStyle narrowStyle = NarrowStyle::None; }; virtual bool rowIsMe(not_null participantPeer) = 0; virtual bool rowCanMuteMembers() = 0; @@ -98,8 +106,8 @@ public: virtual void rowPaintIcon( Painter &p, QRect rect, - IconState state) = 0; - virtual void rowPaintWideBackground(Painter &p, bool selected) = 0; + const IconState &state) = 0; + virtual void rowPaintNarrowBackground(Painter &p, bool selected) = 0; }; class Row final : public PeerListRow { @@ -154,6 +162,8 @@ public: void addActionRipple(QPoint point, Fn updateCallback) override; void stopLastActionRipple() override; + void refreshName(const style::PeerListItem &st) override; + QSize actionSize() const override { return QSize( st::groupCallActiveButton.width, @@ -264,7 +274,7 @@ private: int sizew, int sizeh, PanelMode mode); - [[nodiscard]] static std::tuple UserpicInWideMode( + [[nodiscard]] static std::tuple UserpicInNarrowMode( int x, int y, int sizew, @@ -284,6 +294,15 @@ private: int sizew, int sizeh, PanelMode mode); + void paintNarrowName( + Painter &p, + int x, + int y, + int sizew, + int sizeh, + NarrowStyle style); + [[nodiscard]] RowDelegate::IconState computeIconState( + NarrowStyle style = NarrowStyle::None) const; const not_null _delegate; State _state = State::Inactive; @@ -297,6 +316,7 @@ private: Ui::Animations::Simple _speakingAnimation; // For gray-red/green icon. Ui::Animations::Simple _mutedAnimation; // For gray/red icon. Ui::Animations::Simple _activeAnimation; // For icon cross animation. + Ui::Text::String _narrowName; QString _aboutText; crl::time _speakingLastTime = 0; uint64 _raisedHandRating = 0; @@ -346,8 +366,8 @@ public: void rowPaintIcon( Painter &p, QRect rect, - IconState state) override; - void rowPaintWideBackground(Painter &p, bool selected) override; + const IconState &state) override; + void rowPaintNarrowBackground(Painter &p, bool selected) override; int customRowHeight() override; void customRowPaint( @@ -436,8 +456,11 @@ private: Ui::CrossLineAnimation _inactiveCrossLine; Ui::CrossLineAnimation _coloredCrossLine; - Ui::RoundRect _wideRoundRectSelected; - Ui::RoundRect _wideRoundRect; + Ui::CrossLineAnimation _inactiveNarrowCrossLine; + Ui::CrossLineAnimation _coloredNarrowCrossLine; + Ui::CrossLineAnimation _videoNarrowCrossLine; + Ui::RoundRect _narrowRoundRectSelected; + Ui::RoundRect _narrowRoundRect; rpl::lifetime _lifetime; @@ -749,15 +772,14 @@ bool Row::paintVideo( return true; } -std::tuple Row::UserpicInWideMode( +std::tuple Row::UserpicInNarrowMode( int x, int y, int sizew, int sizeh) { const auto useSize = st::groupCallMembersList.item.photoSize; const auto skipx = (sizew - useSize) / 2; - const auto skipy = (sizeh - useSize) / 2; - return { x + skipx, y + skipy, useSize }; + return { x + skipx, y + st::groupCallNarrowUserpicTop, useSize }; } void Row::paintBlobs( @@ -772,7 +794,7 @@ void Row::paintBlobs( } auto size = sizew; if (mode == PanelMode::Wide) { - std::tie(x, y, size) = UserpicInWideMode(x, y, sizew, sizeh); + std::tie(x, y, size) = UserpicInNarrowMode(x, y, sizew, sizeh); } const auto mutedByMe = (_state == State::MutedByMe); const auto shift = QPointF(x + size / 2., y + size / 2.); @@ -800,7 +822,7 @@ void Row::paintScaledUserpic( PanelMode mode) { auto size = sizew; if (mode == PanelMode::Wide) { - std::tie(x, y, size) = UserpicInWideMode(x, y, sizew, sizeh); + std::tie(x, y, size) = UserpicInNarrowMode(x, y, sizew, sizeh); } if (!_blobsAnimation) { peer()->paintUserpicLeft(p, userpic, x, y, outerWidth, size); @@ -834,6 +856,70 @@ void Row::paintScaledUserpic( _blobsAnimation->userpicCache); } +void Row::paintNarrowName( + Painter &p, + int x, + int y, + int sizew, + int sizeh, + NarrowStyle style) { + if (_narrowName.isEmpty()) { + _narrowName.setText( + st::semiboldTextStyle, + generateShortName(), + Ui::NameTextOptions()); + } + const auto &icon = st::groupCallVideoCrossLine.icon; + const auto added = icon.width() - st::groupCallNarrowIconLess; + const auto available = sizew - 2 * st::normalFont->spacew - added; + const auto use = std::min(available, _narrowName.maxWidth()); + const auto left = (sizew - use - added) / 2; + const auto iconRect = QRect( + left - st::groupCallNarrowIconLess, + y + st::groupCallNarrowIconTop, + icon.width(), + icon.height()); + const auto &state = computeIconState(style); + _delegate->rowPaintIcon(p, iconRect, state); + + p.setPen([&] { + if (style == NarrowStyle::Video) { + return st::groupCallVideoTextFg->p; + } else if (state.speaking == 1. && !state.mutedByMe) { + return st::groupCallMemberActiveIcon->p; + } else if (state.speaking == 0.) { + if (state.active == 1.) { + return st::groupCallMemberInactiveIcon->p; + } else if (state.active == 0.) { + if (state.muted == 1.) { + return state.raisedHand + ? st::groupCallMemberInactiveStatus->p + : st::groupCallMemberMutedIcon->p; + } else if (state.muted == 0.) { + return st::groupCallMemberInactiveIcon->p; + } + } + } + const auto activeInactiveColor = anim::color( + st::groupCallMemberInactiveIcon, + (state.mutedByMe + ? st::groupCallMemberMutedIcon + : st::groupCallMemberActiveIcon), + state.speaking); + return anim::pen( + activeInactiveColor, + st::groupCallMemberMutedIcon, + state.muted); + }()); + const auto nameLeft = iconRect.x() + icon.width(); + const auto nameTop = y + st::groupCallNarrowNameTop; + if (use == available) { + _narrowName.drawLeftElided(p, nameLeft, nameTop, available, sizew); + } else { + _narrowName.drawLeft(p, nameLeft, nameTop, available, sizew); + } +} + auto Row::generatePaintUserpicCallback() -> PaintRoundImageCallback { return [=](Painter &p, int x, int y, int outerWidth, int size) { const auto outer = outerWidth; @@ -852,9 +938,10 @@ void Row::paintComplexUserpic( bool selected) { if (mode == PanelMode::Wide) { if (paintVideo(p, x, y, sizew, sizeh, mode)) { + paintNarrowName(p, x, y, sizew, sizeh, NarrowStyle::Video); return; } - _delegate->rowPaintWideBackground(p, selected); + _delegate->rowPaintNarrowBackground(p, selected); paintRipple(p, x, y, outerWidth); } paintBlobs(p, x, y, sizew, sizeh, mode); @@ -871,6 +958,9 @@ void Row::paintComplexUserpic( sizew, sizeh, mode); + if (mode == PanelMode::Wide) { + paintNarrowName(p, x, y, sizew, sizeh, NarrowStyle::Userpic); + } } int Row::statusIconWidth() const { @@ -1043,18 +1133,23 @@ void Row::paintAction( _actionRipple.reset(); } } + _delegate->rowPaintIcon(p, iconRect, computeIconState()); +} + +RowDelegate::IconState Row::computeIconState(NarrowStyle style) const { const auto speaking = _speakingAnimation.value(_speaking ? 1. : 0.); const auto active = _activeAnimation.value((_state == State::Active) ? 1. : 0.); const auto muted = _mutedAnimation.value( (_state == State::Muted || _state == State::RaisedHand) ? 1. : 0.); const auto mutedByMe = (_state == State::MutedByMe); - _delegate->rowPaintIcon(p, iconRect, { + return { .speaking = speaking, .active = active, .muted = muted, .mutedByMe = (_state == State::MutedByMe), .raisedHand = (_state == State::RaisedHand), - }); + .narrowStyle = style, + }; } void Row::refreshStatus() { @@ -1115,6 +1210,11 @@ void Row::addActionRipple(QPoint point, Fn updateCallback) { _actionRipple->add(point - st::groupCallActiveButton.rippleAreaPosition); } +void Row::refreshName(const style::PeerListItem &st) { + PeerListRow::refreshName(st); + _narrowName = Ui::Text::String(); +} + void Row::stopLastActionRipple() { if (_actionRipple) { _actionRipple->lastStop(); @@ -1130,14 +1230,22 @@ MembersController::MembersController( , _raisedHandStatusRemoveTimer([=] { scheduleRaisedHandStatusRemove(); }) , _inactiveCrossLine(st::groupCallMemberInactiveCrossLine) , _coloredCrossLine(st::groupCallMemberColoredCrossLine) -, _wideRoundRectSelected(ImageRoundRadius::Large, st::groupCallMembersBgOver) -, _wideRoundRect(ImageRoundRadius::Large, st::groupCallMembersBg) { +, _inactiveNarrowCrossLine(st::groupCallNarrowInactiveCrossLine) +, _coloredNarrowCrossLine(st::groupCallNarrowColoredCrossLine) +, _videoNarrowCrossLine(st::groupCallVideoCrossLine) +, _narrowRoundRectSelected( + ImageRoundRadius::Large, + st::groupCallMembersBgOver) +, _narrowRoundRect(ImageRoundRadius::Large, st::groupCallMembersBg) { setupListChangeViewers(); style::PaletteChanged( ) | rpl::start_with_next([=] { _inactiveCrossLine.invalidate(); _coloredCrossLine.invalidate(); + _inactiveNarrowCrossLine.invalidate(); + _coloredNarrowCrossLine.invalidate(); + _videoNarrowCrossLine.invalidate(); }, _lifetime); rpl::combine( @@ -1812,8 +1920,14 @@ void MembersController::scheduleRaisedHandStatusRemove() { void MembersController::rowPaintIcon( Painter &p, QRect rect, - IconState state) { - const auto &greenIcon = st::groupCallMemberColoredCrossLine.icon; + const IconState &state) { + const auto narrowUserpic = (state.narrowStyle == NarrowStyle::Userpic); + const auto narrowVideo = (state.narrowStyle == NarrowStyle::Video); + const auto &greenIcon = narrowVideo + ? st::groupCallVideoCrossLine.icon + : narrowUserpic + ? st::groupCallNarrowColoredCrossLine.icon + : st::groupCallMemberColoredCrossLine.icon; const auto left = rect.x() + (rect.width() - greenIcon.width()) / 2; const auto top = rect.y() + (rect.height() - greenIcon.height()) / 2; if (state.speaking == 1. && !state.mutedByMe) { @@ -1823,25 +1937,44 @@ void MembersController::rowPaintIcon( } else if (state.speaking == 0.) { if (state.active == 1.) { // Just gray icon, no cross, no coloring. - st::groupCallMemberInactiveCrossLine.icon.paintInCenter(p, rect); + const auto &grayIcon = narrowVideo + ? st::groupCallVideoCrossLine.icon + : narrowUserpic + ? st::groupCallNarrowInactiveCrossLine.icon + : st::groupCallMemberInactiveCrossLine.icon; + grayIcon.paintInCenter(p, rect); return; } else if (state.active == 0.) { if (state.muted == 1.) { if (state.raisedHand) { + // #TODO narrow mode icon st::groupCallMemberRaisedHand.paintInCenter(p, rect); return; } // Red crossed icon, colorized once, cached as last frame. - _coloredCrossLine.paint( + auto &line = narrowVideo + ? _videoNarrowCrossLine + : narrowUserpic + ? _coloredNarrowCrossLine + : _coloredCrossLine; + const auto color = narrowVideo + ? std::nullopt + : std::make_optional(st::groupCallMemberMutedIcon->c); + line.paint( p, left, top, 1., - st::groupCallMemberMutedIcon->c); + color); return; } else if (state.muted == 0.) { // Gray crossed icon, no coloring, cached as last frame. - _inactiveCrossLine.paint(p, left, top, 1.); + auto &line = narrowVideo + ? _videoNarrowCrossLine + : narrowUserpic + ? _inactiveNarrowCrossLine + : _inactiveCrossLine; + line.paint(p, left, top, 1.); return; } } @@ -1856,15 +1989,23 @@ void MembersController::rowPaintIcon( activeInactiveColor, st::groupCallMemberMutedIcon, state.muted); + const auto color = narrowVideo + ? std::nullopt + : std::make_optional(iconColor); // Don't use caching of the last frame, // because 'muted' may animate color. const auto crossProgress = std::min(1. - state.active, 0.9999); - _inactiveCrossLine.paint(p, left, top, crossProgress, iconColor); + auto &line = narrowVideo + ? _videoNarrowCrossLine + : narrowUserpic + ? _inactiveNarrowCrossLine + : _inactiveCrossLine; + line.paint(p, left, top, crossProgress, color); } -void MembersController::rowPaintWideBackground(Painter &p, bool selected) { - (selected ? _wideRoundRectSelected : _wideRoundRect).paint( +void MembersController::rowPaintNarrowBackground(Painter &p, bool selected) { + (selected ? _narrowRoundRectSelected : _narrowRoundRect).paint( p, { QPoint(st::groupCallNarrowSkip, 0), st::groupCallNarrowSize }); } diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp index d2940855a..87cf7e333 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp @@ -996,6 +996,7 @@ void Panel::raiseControls() { void Panel::setupPinnedVideo() { _pinnedVideo.create(widget()); _pinnedVideo->setVisible(_mode == PanelMode::Wide); + _pinnedVideo->setAttribute(Qt::WA_OpaquePaintEvent, true); raiseControls(); @@ -1026,7 +1027,7 @@ void Panel::setupPinnedVideo() { }, _pinnedTrackLifetime); _pinnedVideo->paintRequest( - ) | rpl::start_with_next([=] { + ) | rpl::start_with_next([=](QRect clip) { const auto [image, rotation] = track->frameOriginalWithRotation(); if (image.isNull()) { diff --git a/Telegram/lib_ui b/Telegram/lib_ui index b0c5d9220..52326fffd 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit b0c5d9220bb8f0fc614712167c8276504e60b224 +Subproject commit 52326fffd0297271f5d56c3c2fefd2bfb453f91a