Show segments in expanded stories list.

This commit is contained in:
John Preston 2023-07-17 19:59:27 +04:00
parent 89bd3c10c5
commit 5f4dcc5eb6
11 changed files with 218 additions and 72 deletions

View File

@ -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";

View File

@ -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<Ui::RoundImageCheckboxSegment> segments) {
std::vector<Ui::OutlineSegment> segments) {
Expects(_checkbox != nullptr);
_checkbox->setCustomizedSegments(std::move(segments));

View File

@ -36,7 +36,7 @@ class SlideWrap;
class FlatLabel;
struct ScrollToRequest;
class PopupMenu;
struct RoundImageCheckboxSegment;
struct OutlineSegment;
} // namespace Ui
using PaintRoundImageCallback = Fn<void(
@ -204,7 +204,7 @@ public:
setCheckedInternal(checked, animated);
}
void setCustomizedCheckSegments(
std::vector<Ui::RoundImageCheckboxSegment> segments);
std::vector<Ui::OutlineSegment> segments);
void setHidden(bool hidden) {
_hidden = hidden;
}

View File

@ -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<Ui::RoundImageCheckboxSegment> PrepareSegments(
[[nodiscard]] std::vector<Ui::OutlineSegment> PrepareSegments(
int count,
int unread,
const QBrush &unreadBrush) {
Expects(unread <= count);
Expects(count > 0);
auto result = std::vector<Ui::RoundImageCheckboxSegment>();
auto result = std::vector<Ui::OutlineSegment>();
const auto add = [&](bool unread) {
result.push_back({
.brush = unread ? unreadBrush : st::dialogsUnreadBgMuted->b,

View File

@ -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*> item) {
}
}
void List::validateSegments(
not_null<Item*> 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*> 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.);

View File

@ -22,6 +22,7 @@ struct DialogsStoriesList;
namespace Ui {
class PopupMenu;
struct OutlineSegment;
} // namespace Ui
namespace Dialogs::Stories {
@ -64,6 +65,7 @@ public:
not_null<QWidget*> parent,
const style::DialogsStoriesList &st,
rpl::producer<Content> content);
~List();
void setExpandedHeight(int height, bool momentum = false);
void setLayoutConstraints(
@ -100,6 +102,7 @@ private:
Element element;
QImage nameCache;
QColor nameCacheColor;
std::vector<Ui::OutlineSegment> 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*> item,
const QBrush &brush,
float64 line,
bool forUnread);
[[nodiscard]] Layout computeLayout();
[[nodiscard]] Layout computeLayout(float64 expanded) const;

View File

@ -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<OutlineSegment> &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

View File

@ -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<OutlineSegment> &segments,
float64 fromFullProgress = 1.);
} // namespace Ui

View File

@ -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 <QtCore/QCoreApplication>
@ -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<QBrush> fg) {
}
void RoundImageCheckbox::setCustomizedSegments(
std::vector<Segment> segments) {
std::vector<Ui::OutlineSegment> segments) {
_segments = std::move(segments);
}

View File

@ -15,6 +15,8 @@ enum class ImageRoundRadius;
namespace Ui {
struct OutlineSegment;
class RoundCheckbox {
public:
RoundCheckbox(const style::RoundCheckbox &st, Fn<void()> updateCallback);
@ -45,26 +47,22 @@ private:
};
struct RoundImageCheckboxSegment {
QBrush brush;
float64 width = 0.;
};
class RoundImageCheckbox {
public:
using Segment = RoundImageCheckboxSegment;
using PaintRoundImage = Fn<void(Painter &p, int x, int y, int outerWidth, int size)>;
RoundImageCheckbox(
const style::RoundImageCheckbox &st,
Fn<void()> updateCallback,
PaintRoundImage &&paintRoundImage,
Fn<std::optional<int>(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<QBrush> fg);
void setCustomizedSegments(std::vector<Segment> segments);
void setCustomizedSegments(std::vector<OutlineSegment> segments);
bool checked() const {
return _check.checked();
@ -91,7 +89,7 @@ private:
RoundCheckbox _check;
//std::optional<QBrush> _fgOverride;
std::vector<Segment> _segments;
std::vector<OutlineSegment> _segments;
};

View File

@ -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