diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 68a00520e5..0fea4928d0 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2408,8 +2408,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_add_contact" = "Create"; "lng_add_contact_button" = "New contact"; "lng_contacts_header" = "Contacts"; -"lng_contacts_by_online" = "Sorted by last seen time"; -"lng_contacts_by_name" = "Sorted by name"; "lng_contacts_hidden_stories" = "Hidden Stories"; "lng_contacts_stories_status#one" = "{count} story"; "lng_contacts_stories_status#other" = "{count} stories"; diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp index 777e952b9d..bbfb2acb13 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp @@ -10,13 +10,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/session/session_show.h" #include "main/main_session.h" #include "mainwidget.h" +#include "ui/effects/loading_element.h" +#include "ui/effects/outline_segments.h" +#include "ui/effects/round_checkbox.h" +#include "ui/effects/ripple_animation.h" #include "ui/widgets/multi_select.h" #include "ui/widgets/labels.h" #include "ui/widgets/scroll_area.h" #include "ui/widgets/popup_menu.h" -#include "ui/effects/loading_element.h" -#include "ui/effects/round_checkbox.h" -#include "ui/effects/ripple_animation.h" #include "ui/empty_userpic.h" #include "ui/wrap/slide_wrap.h" #include "ui/text/text_options.h" @@ -894,7 +895,7 @@ void PeerListRow::setCheckedInternal(bool checked, anim::type animated) { } void PeerListRow::setCustomizedCheckSegments( - std::vector segments) { + std::vector segments) { Expects(_checkbox != nullptr); _checkbox->setCustomizedSegments(std::move(segments)); diff --git a/Telegram/SourceFiles/boxes/peer_list_box.h b/Telegram/SourceFiles/boxes/peer_list_box.h index 4252b5fa25..ac8ab554b6 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.h +++ b/Telegram/SourceFiles/boxes/peer_list_box.h @@ -36,7 +36,7 @@ class SlideWrap; class FlatLabel; struct ScrollToRequest; class PopupMenu; -struct RoundImageCheckboxSegment; +struct OutlineSegment; } // namespace Ui using PaintRoundImageCallback = Fn segments); + std::vector segments); void setHidden(bool hidden) { _hidden = hidden; } diff --git a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp index 18d2d514f5..10e393b562 100644 --- a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp @@ -37,6 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "history/history_item.h" #include "dialogs/dialogs_main_list.h" +#include "ui/effects/outline_segments.h" #include "ui/wrap/slide_wrap.h" #include "window/window_session_controller.h" // showAddContact() #include "base/unixtime.h" @@ -53,14 +54,14 @@ namespace { constexpr auto kSortByOnlineThrottle = 3 * crl::time(1000); constexpr auto kSearchPerPage = 50; -[[nodiscard]] std::vector PrepareSegments( +[[nodiscard]] std::vector PrepareSegments( int count, int unread, const QBrush &unreadBrush) { Expects(unread <= count); Expects(count > 0); - auto result = std::vector(); + auto result = std::vector(); const auto add = [&](bool unread) { result.push_back({ .brush = unread ? unreadBrush : st::dialogsUnreadBgMuted->b, diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.cpp index ad39539dde..6e89cb0438 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "dialogs/ui/dialogs_stories_list.h" #include "lang/lang_keys.h" +#include "ui/effects/outline_segments.h" #include "ui/widgets/menu/menu_add_action_callback_factory.h" #include "ui/widgets/popup_menu.h" #include "ui/painter.h" @@ -41,6 +42,7 @@ struct List::Layout { QPointF geometryShift; float64 expandedRatio = 0.; float64 ratio = 0.; + float64 segmentsSpinProgress = 0.; float64 thumbnailLeft = 0.; float64 photoLeft = 0.; float64 left = 0.; @@ -72,6 +74,8 @@ List::List( resize(0, _data.empty() ? 0 : st.full.height); } +List::~List() = default; + void List::showContent(Content &&content) { if (_content == content) { return; @@ -151,12 +155,13 @@ void List::requestExpanded(bool expanded) { if (_expanded != expanded) { _expanded = expanded; const auto from = _expanded ? 0. : 1.; - const auto till = _expanded ? 1. : 0.; + const auto till = _expanded ? 2. : 0.; + const auto duration = (_expanded ? 2 : 1) * st::slideWrapDuration; _expandedAnimation.start([=] { checkForFullState(); update(); _collapsedGeometryChanged.fire({}); - }, from, till, st::slideWrapDuration, anim::sineInOut); + }, from, till, duration, anim::sineInOut); } _toggleExpandedRequests.fire_copy(_expanded); } @@ -192,10 +197,13 @@ List::Layout List::computeLayout() { updateExpanding( _lastExpandedHeight * _expandCatchUpAnimation.value(1.), _st.full.height); - return computeLayout(_expandedAnimation.value(_expanded ? 1. : 0.)); + return computeLayout(_expandedAnimation.value(_expanded ? 2. : 0.)); } List::Layout List::computeLayout(float64 expanded) const { + const auto segmentsSpinProgress = expanded / 2.; + expanded = std::min(expanded, 1.); + const auto &st = _st.small; const auto &full = _st.full; const auto expandedRatio = _lastRatio; @@ -251,6 +259,7 @@ List::Layout List::computeLayout(float64 expanded) const { : 0.)), .expandedRatio = expandedRatio, .ratio = ratio, + .segmentsSpinProgress = segmentsSpinProgress, .thumbnailLeft = thumbnailLeft, .photoLeft = photoLeft, .left = thumbnailLeft - photoLeft, @@ -385,6 +394,11 @@ void List::paintEvent(QPaintEvent *e) { } } }; + auto gradient = QLinearGradient(); + gradient.setStops({ + { 0., st::groupCallLive1->c }, + { 1., st::groupCallMuted1->c }, + }); enumerate([&](Single single) { // Name. if (const auto full = single.itemFull) { @@ -409,30 +423,35 @@ void List::paintEvent(QPaintEvent *e) { photo); const auto small = single.itemSmall; const auto itemFull = single.itemFull; - const auto smallUnread = small && small->element.unreadCount; - const auto fullUnread = itemFull && itemFull->element.unreadCount; - const auto unreadOpacity = (smallUnread && fullUnread) + const auto smallUnread = (small && small->element.unreadCount); + const auto fullUnreadCount = itemFull + ? itemFull->element.unreadCount + : 0; + const auto unreadOpacity = (smallUnread && fullUnreadCount) ? 1. : smallUnread ? (1. - expandRatio) - : fullUnread + : fullUnreadCount ? expandRatio : 0.; if (unreadOpacity > 0.) { p.setOpacity(unreadOpacity); - const auto outerAdd = 2 * line; + const auto outerAdd = 1.5 * line; const auto outer = userpic.marginsAdded( { outerAdd, outerAdd, outerAdd, outerAdd }); - p.setPen(Qt::NoPen); - auto gradient = QLinearGradient( - userpic.topRight(), - userpic.bottomLeft()); - gradient.setStops({ - { 0., st::groupCallLive1->c }, - { 1., st::groupCallMuted1->c }, - }); - p.setBrush(gradient); - p.drawEllipse(outer); + gradient.setStart(userpic.topRight()); + gradient.setFinalStop(userpic.bottomLeft()); + if (!fullUnreadCount) { + p.setPen(QPen(gradient, line)); + p.drawEllipse(outer); + } else { + validateSegments(itemFull, gradient, line, true); + Ui::PaintOutlineSegments( + p, + outer, + itemFull->segments, + layout.segmentsSpinProgress); + } } p.setOpacity(1.); }, [&](Single single) { @@ -447,30 +466,37 @@ void List::paintEvent(QPaintEvent *e) { const auto small = single.itemSmall; const auto itemFull = single.itemFull; const auto smallUnread = small && small->element.unreadCount; - const auto fullUnread = itemFull && itemFull->element.unreadCount; + const auto fullUnreadCount = itemFull + ? itemFull->element.unreadCount + : 0; + const auto fullCount = itemFull ? itemFull->element.count : 0; // White circle with possible read gray line. - const auto hasReadLine = (itemFull && !fullUnread); + const auto hasReadLine = (itemFull && fullUnreadCount < fullCount); p.setOpacity((small && itemFull) ? 1. : small ? (1. - expandRatio) : expandRatio); - if (hasReadLine) { - auto color = st::dialogsUnreadBgMuted->c; - if (small) { - color.setAlphaF(color.alphaF() * expandRatio); - } - auto pen = QPen(color); - pen.setWidthF(lineRead); - p.setPen(pen); - } else { - p.setPen(Qt::NoPen); - } const auto add = line + (hasReadLine ? (lineRead / 2.) : 0.); const auto rect = userpic.marginsAdded({ add, add, add, add }); + p.setPen(Qt::NoPen); p.setBrush(st::dialogsBg); p.drawEllipse(rect); + if (hasReadLine) { + if (small && !small->element.unreadCount) { + p.setOpacity(expandRatio); + } + validateSegments( + itemFull, + st::dialogsUnreadBgMuted->b, + lineRead, + false); + Ui::PaintOutlineSegments( + p, + rect, + itemFull->segments); + } // Userpic. if (itemFull == small) { @@ -514,6 +540,36 @@ void List::validateThumbnail(not_null item) { } } +void List::validateSegments( + not_null item, + const QBrush &brush, + float64 line, + bool forUnread) { + const auto count = item->element.count; + const auto unread = item->element.unreadCount; + if (int(item->segments.size()) != count) { + item->segments.resize(count); + } + auto i = 0; + if (forUnread) { + for (; i != count - unread; ++i) { + item->segments[i].width = 0.; + } + for (; i != count; ++i) { + item->segments[i].brush = brush; + item->segments[i].width = line; + } + } else { + for (; i != count - unread; ++i) { + item->segments[i].brush = brush; + item->segments[i].width = line; + } + for (; i != count; ++i) { + item->segments[i].width = 0.; + } + } +} + void List::validateName(not_null item) { const auto &color = st::dialogsNameFg; if (!item->nameCache.isNull() && item->nameCacheColor == color->c) { @@ -680,8 +736,8 @@ void List::setLayoutConstraints( } List::CollapsedGeometry List::collapsedGeometryCurrent() const { - const auto expanded = _expandedAnimation.value(_expanded ? 1. : 0.); - if (expanded == 1.) { + const auto expanded = _expandedAnimation.value(_expanded ? 2. : 0.); + if (expanded >= 1.) { return { QRect(), 1. }; } const auto layout = computeLayout(0.); diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.h b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.h index d0ecc1428e..ef3b2917e0 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.h @@ -22,6 +22,7 @@ struct DialogsStoriesList; namespace Ui { class PopupMenu; +struct OutlineSegment; } // namespace Ui namespace Dialogs::Stories { @@ -64,6 +65,7 @@ public: not_null parent, const style::DialogsStoriesList &st, rpl::producer content); + ~List(); void setExpandedHeight(int height, bool momentum = false); void setLayoutConstraints( @@ -100,6 +102,7 @@ private: Element element; QImage nameCache; QColor nameCacheColor; + std::vector segments; bool subscribed = false; }; struct Data { @@ -134,6 +137,11 @@ private: void updateGeometry(); [[nodiscard]] QRect countSmallGeometry() const; void updateExpanding(int expandingHeight, int expandedHeight); + void validateSegments( + not_null item, + const QBrush &brush, + float64 line, + bool forUnread); [[nodiscard]] Layout computeLayout(); [[nodiscard]] Layout computeLayout(float64 expanded) const; diff --git a/Telegram/SourceFiles/ui/effects/outline_segments.cpp b/Telegram/SourceFiles/ui/effects/outline_segments.cpp new file mode 100644 index 0000000000..2482217e7c --- /dev/null +++ b/Telegram/SourceFiles/ui/effects/outline_segments.cpp @@ -0,0 +1,73 @@ +/* +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 "ui/effects/outline_segments.h" + +namespace Ui { + +void PaintOutlineSegments( + QPainter &p, + QRectF ellipse, + const std::vector &segments, + float64 fromFullProgress) { + Expects(!segments.empty()); + + p.setBrush(Qt::NoBrush); + const auto count = int(segments.size()); + if (count == 1) { + p.setPen(QPen(segments.front().brush, segments.front().width)); + p.drawEllipse(ellipse); + return; + } + const auto small = 160; + const auto full = arc::kFullLength; + const auto separator = (full > 1.1 * small * count) + ? small + : (full / (count * 1.1)); + const auto left = full - (separator * count); + const auto length = left / float64(count); + const auto step = length + separator; + const auto spin = separator * (1. - fromFullProgress); + + auto start = 0. + (arc::kQuarterLength + (separator / 2)) + (3. * spin); + auto pen = QPen( + segments.back().brush, + segments.back().width, + Qt::SolidLine, + Qt::RoundCap); + p.setPen(pen); + for (auto i = 0; i != count;) { + const auto &segment = segments[count - (++i)]; + if (!segment.width) { + start += length + separator; + continue; + } else if (pen.brush() != segment.brush + || pen.widthF() != segment.width) { + pen = QPen( + segment.brush, + segment.width, + Qt::SolidLine, + Qt::RoundCap); + p.setPen(pen); + } + const auto from = int(base::SafeRound(start)); + const auto till = start + length; + auto added = spin; + for (; i != count;) { + start += length + separator; + const auto &next = segments[count - (++i)]; + if (next.width) { + --i; + break; + } + added += (separator + length) * (1. - fromFullProgress); + } + p.drawArc(ellipse, from, int(base::SafeRound(till + added)) - from); + } +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/effects/outline_segments.h b/Telegram/SourceFiles/ui/effects/outline_segments.h new file mode 100644 index 0000000000..fd48355953 --- /dev/null +++ b/Telegram/SourceFiles/ui/effects/outline_segments.h @@ -0,0 +1,23 @@ +/* +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 + +namespace Ui { + +struct OutlineSegment { + QBrush brush; + float64 width = 0.; +}; + +void PaintOutlineSegments( + QPainter &p, + QRectF ellipse, + const std::vector &segments, + float64 fromFullProgress = 1.); + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/effects/round_checkbox.cpp b/Telegram/SourceFiles/ui/effects/round_checkbox.cpp index a31eb708ec..78996a89cb 100644 --- a/Telegram/SourceFiles/ui/effects/round_checkbox.cpp +++ b/Telegram/SourceFiles/ui/effects/round_checkbox.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/rp_widget.h" #include "ui/ui_utility.h" #include "ui/painter.h" +#include "ui/effects/outline_segments.h" #include "ui/image/image_prepare.h" #include @@ -368,6 +369,10 @@ RoundImageCheckbox::RoundImageCheckbox( , _check(_st.check, _updateCallback) { } +RoundImageCheckbox::RoundImageCheckbox(RoundImageCheckbox&&) = default; + +RoundImageCheckbox::~RoundImageCheckbox() = default; + void RoundImageCheckbox::paint(Painter &p, int x, int y, int outerWidth) const { auto selectionLevel = _selection.value(checked() ? 1. : 0.); if (_selection.animating()) { @@ -416,26 +421,7 @@ void RoundImageCheckbox::paint(Painter &p, int x, int y, int outerWidth) const { p.drawRoundedRect(outline, *radius, *radius); } } else { - const auto small = 160; - const auto full = arc::kFullLength; - const auto separator = (full > 1.1 * small * segments) - ? small - : full / (segments * 1.1); - const auto left = full - (separator * segments); - const auto length = left / float64(segments); - - auto start = 0. + (arc::kQuarterLength + (separator / 2)); - for (const auto &segment : ranges::views::reverse(_segments)) { - p.setPen(QPen( - segment.brush, - segment.width, - Qt::SolidLine, - Qt::RoundCap)); - const auto from = int(base::SafeRound(start)); - const auto till = int(base::SafeRound(start + length)); - p.drawArc(outline, from, till - from); - start += length + separator; - } + PaintOutlineSegments(p, outline, _segments); } p.setOpacity(1.); } @@ -511,7 +497,7 @@ void RoundImageCheckbox::setColorOverride(std::optional fg) { } void RoundImageCheckbox::setCustomizedSegments( - std::vector segments) { + std::vector segments) { _segments = std::move(segments); } diff --git a/Telegram/SourceFiles/ui/effects/round_checkbox.h b/Telegram/SourceFiles/ui/effects/round_checkbox.h index 9247c226e7..ff24ac5970 100644 --- a/Telegram/SourceFiles/ui/effects/round_checkbox.h +++ b/Telegram/SourceFiles/ui/effects/round_checkbox.h @@ -15,6 +15,8 @@ enum class ImageRoundRadius; namespace Ui { +struct OutlineSegment; + class RoundCheckbox { public: RoundCheckbox(const style::RoundCheckbox &st, Fn updateCallback); @@ -45,26 +47,22 @@ private: }; -struct RoundImageCheckboxSegment { - QBrush brush; - float64 width = 0.; -}; - class RoundImageCheckbox { public: - using Segment = RoundImageCheckboxSegment; using PaintRoundImage = Fn; RoundImageCheckbox( const style::RoundImageCheckbox &st, Fn updateCallback, PaintRoundImage &&paintRoundImage, Fn(int size)> roundingRadius = nullptr); + RoundImageCheckbox(RoundImageCheckbox&&); + ~RoundImageCheckbox(); void paint(Painter &p, int x, int y, int outerWidth) const; float64 checkedAnimationRatio() const; void setColorOverride(std::optional fg); - void setCustomizedSegments(std::vector segments); + void setCustomizedSegments(std::vector segments); bool checked() const { return _check.checked(); @@ -91,7 +89,7 @@ private: RoundCheckbox _check; //std::optional _fgOverride; - std::vector _segments; + std::vector _segments; }; diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 08b9990c45..d5ea92dcb9 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -267,6 +267,8 @@ PRIVATE ui/effects/glare.h ui/effects/loading_element.cpp ui/effects/loading_element.h + ui/effects/outline_segments.cpp + ui/effects/outline_segments.h ui/effects/premium_graphics.cpp ui/effects/premium_graphics.h ui/effects/premium_stars.cpp