mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-02-24 17:26:58 +00:00
Show user photos overview in PeerShortInfoBox.
This commit is contained in:
parent
64f6b86739
commit
360a92c198
@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace {
|
||||
|
||||
constexpr auto kShadowMaxAlpha = 80;
|
||||
constexpr auto kInactiveBarOpacity = 0.5;
|
||||
|
||||
} // namespace
|
||||
|
||||
@ -52,6 +53,11 @@ PeerShortInfoBox::PeerShortInfoBox(
|
||||
) | rpl::start_with_next([=](PeerShortInfoUserpic &&value) {
|
||||
applyUserpic(std::move(value));
|
||||
}, lifetime());
|
||||
|
||||
style::PaletteChanged(
|
||||
) | rpl::start_with_next([=] {
|
||||
refreshBarImages();
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
PeerShortInfoBox::~PeerShortInfoBox() = default;
|
||||
@ -60,6 +66,10 @@ rpl::producer<> PeerShortInfoBox::openRequests() const {
|
||||
return _openRequests.events();
|
||||
}
|
||||
|
||||
rpl::producer<int> PeerShortInfoBox::moveRequests() const {
|
||||
return _moveRequests.events();
|
||||
}
|
||||
|
||||
void PeerShortInfoBox::prepare() {
|
||||
addButton(tr::lng_close(), [=] { closeBox(); });
|
||||
|
||||
@ -79,6 +89,8 @@ void PeerShortInfoBox::prepare() {
|
||||
) | rpl::start_with_next([=](int height) {
|
||||
setDimensions(st::shortInfoWidth, st::shortInfoWidth + height);
|
||||
}, lifetime());
|
||||
|
||||
setMouseTracking(true);
|
||||
}
|
||||
|
||||
void PeerShortInfoBox::prepareRows() {
|
||||
@ -143,7 +155,6 @@ void PeerShortInfoBox::prepareRows() {
|
||||
tr::lng_info_username_label(),
|
||||
usernameValue() | Ui::Text::ToWithEntities(),
|
||||
tr::lng_context_copy_mention(tr::now));
|
||||
|
||||
}
|
||||
|
||||
RectParts PeerShortInfoBox::customCornersFilling() {
|
||||
@ -192,8 +203,72 @@ void PeerShortInfoBox::paintEvent(QPaintEvent *e) {
|
||||
_videoInstance->markFrameShown();
|
||||
}
|
||||
|
||||
if (_shadow.isNull()) {
|
||||
_shadow = Images::GenerateShadow(
|
||||
paintBars(p);
|
||||
paintShadow(p);
|
||||
}
|
||||
|
||||
void PeerShortInfoBox::mouseMoveEvent(QMouseEvent *e) {
|
||||
//const auto x = e->pos().x();
|
||||
const auto cursor = (_count > 1)
|
||||
? style::cur_pointer
|
||||
: style::cur_default;
|
||||
//const auto cursor = (_index > 0 && x < st::shortInfoWidth / 3)
|
||||
// ? style::cur_pointer
|
||||
// : (_index + 1 < _count && x >= st::shortInfoWidth / 3)
|
||||
// ? style::cur_pointer
|
||||
// : style::cur_default;
|
||||
if (_cursor != cursor) {
|
||||
_cursor = cursor;
|
||||
setCursor(_cursor);
|
||||
}
|
||||
}
|
||||
|
||||
void PeerShortInfoBox::mousePressEvent(QMouseEvent *e) {
|
||||
const auto x = e->pos().x();
|
||||
if (e->button() != Qt::LeftButton) {
|
||||
return;
|
||||
} else if (/*_index > 0 && */x < st::shortInfoWidth / 3) {
|
||||
_moveRequests.fire(-1);
|
||||
} else if (/*_index + 1 < _count && */x >= st::shortInfoWidth / 3) {
|
||||
_moveRequests.fire(1);
|
||||
}
|
||||
}
|
||||
|
||||
void PeerShortInfoBox::paintBars(QPainter &p) {
|
||||
const auto height = st::shortInfoLinePadding * 2 + st::shortInfoLine;
|
||||
if (_shadowTop.isNull()) {
|
||||
_shadowTop = Images::GenerateShadow(height, kShadowMaxAlpha, 0);
|
||||
}
|
||||
const auto shadowRect = QRect(0, 0, st::shortInfoWidth, height);
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
p.drawImage(
|
||||
shadowRect,
|
||||
_shadowTop,
|
||||
QRect(0, 0, _shadowTop.width(), height * factor));
|
||||
if (!_smallWidth) {
|
||||
return;
|
||||
}
|
||||
const auto top = st::shortInfoLinePadding;
|
||||
const auto skip = st::shortInfoLineSkip;
|
||||
const auto full = (st::shortInfoWidth - 2 * top - (_count - 1) * skip);
|
||||
const auto width = full / float64(_count);
|
||||
for (auto i = 0; i != _count; ++i) {
|
||||
const auto left = top + i * (width + skip);
|
||||
const auto right = left + width;
|
||||
p.setOpacity((i == _index) ? 1. : kInactiveBarOpacity);
|
||||
p.drawImage(
|
||||
qRound(left),
|
||||
top,
|
||||
((qRound(right) == qRound(left) + _smallWidth)
|
||||
? _barSmall
|
||||
: _barLarge));
|
||||
}
|
||||
p.setOpacity(1.);
|
||||
}
|
||||
|
||||
void PeerShortInfoBox::paintShadow(QPainter &p) {
|
||||
if (_shadowBottom.isNull()) {
|
||||
_shadowBottom = Images::GenerateShadow(
|
||||
st::shortInfoShadowHeight,
|
||||
0,
|
||||
kShadowMaxAlpha);
|
||||
@ -206,11 +281,11 @@ void PeerShortInfoBox::paintEvent(QPaintEvent *e) {
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
p.drawImage(
|
||||
shadowRect,
|
||||
_shadow,
|
||||
_shadowBottom,
|
||||
QRect(
|
||||
0,
|
||||
0,
|
||||
_shadow.width(),
|
||||
_shadowBottom.width(),
|
||||
st::shortInfoShadowHeight * factor));
|
||||
}
|
||||
|
||||
@ -261,14 +336,24 @@ rpl::producer<TextWithEntities> PeerShortInfoBox::aboutValue() const {
|
||||
}
|
||||
|
||||
void PeerShortInfoBox::applyUserpic(PeerShortInfoUserpic &&value) {
|
||||
if (_index != value.index) {
|
||||
_index = value.index;
|
||||
update();
|
||||
}
|
||||
if (_count != value.count) {
|
||||
_count = value.count;
|
||||
refreshBarImages();
|
||||
update();
|
||||
}
|
||||
if (!value.photo.isNull()
|
||||
&& _userpicImage.cacheKey() != value.photo.cacheKey()) {
|
||||
_userpicImage = std::move(value.photo);
|
||||
update();
|
||||
}
|
||||
if (value.videoDocument
|
||||
&& (!_videoInstance
|
||||
|| _videoInstance->shared() != value.videoDocument)) {
|
||||
if (!value.videoDocument) {
|
||||
_videoInstance = nullptr;
|
||||
} else if (!_videoInstance
|
||||
|| _videoInstance->shared() != value.videoDocument) {
|
||||
const auto frame = currentVideoFrame();
|
||||
if (!frame.isNull()) {
|
||||
_userpicImage = frame;
|
||||
@ -340,6 +425,40 @@ void PeerShortInfoBox::streamingReady(Media::Streaming::Information &&info) {
|
||||
update(coverRect());
|
||||
}
|
||||
|
||||
void PeerShortInfoBox::refreshBarImages() {
|
||||
if (_count < 2) {
|
||||
_smallWidth = _largeWidth = 0;
|
||||
_barSmall = _barLarge = QImage();
|
||||
return;
|
||||
}
|
||||
const auto width = st::shortInfoWidth - 2 * st::shortInfoLinePadding;
|
||||
_smallWidth = (width - (_count - 1) * st::shortInfoLineSkip) / _count;
|
||||
if (_smallWidth < st::shortInfoLine) {
|
||||
_smallWidth = _largeWidth = 0;
|
||||
_barSmall = _barLarge = QImage();
|
||||
return;
|
||||
}
|
||||
_largeWidth = _smallWidth + 1;
|
||||
const auto makeBar = [](int size) {
|
||||
const auto radius = st::shortInfoLine / 2.;
|
||||
auto result = QImage(
|
||||
QSize(size, st::shortInfoLine) * style::DevicePixelRatio(),
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
result.setDevicePixelRatio(style::DevicePixelRatio());
|
||||
result.fill(Qt::transparent);
|
||||
auto p = QPainter(&result);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(st::groupCallVideoTextFg);
|
||||
p.drawRoundedRect(0, 0, size, st::shortInfoLine, radius, radius);
|
||||
p.end();
|
||||
|
||||
return result;
|
||||
};
|
||||
_barSmall = makeBar(_smallWidth);
|
||||
_barLarge = makeBar(_largeWidth);
|
||||
}
|
||||
|
||||
QRect PeerShortInfoBox::coverRect() const {
|
||||
return QRect(0, 0, st::shortInfoWidth, st::shortInfoWidth);
|
||||
}
|
||||
|
@ -58,6 +58,7 @@ public:
|
||||
~PeerShortInfoBox();
|
||||
|
||||
[[nodiscard]] rpl::producer<> openRequests() const;
|
||||
[[nodiscard]] rpl::producer<int> moveRequests() const;
|
||||
|
||||
private:
|
||||
void prepare() override;
|
||||
@ -66,6 +67,11 @@ private:
|
||||
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void mouseMoveEvent(QMouseEvent *e) override;
|
||||
void mousePressEvent(QMouseEvent *e) override;
|
||||
|
||||
void paintBars(QPainter &p);
|
||||
void paintShadow(QPainter &p);
|
||||
|
||||
[[nodiscard]] QImage currentVideoFrame() const;
|
||||
|
||||
@ -84,6 +90,8 @@ private:
|
||||
void handleStreamingError(Media::Streaming::Error &&error);
|
||||
void streamingReady(Media::Streaming::Information &&info);
|
||||
|
||||
void refreshBarImages();
|
||||
|
||||
const PeerShortInfoType _type = PeerShortInfoType::User;
|
||||
|
||||
rpl::variable<PeerShortInfoFields> _fields;
|
||||
@ -94,11 +102,22 @@ private:
|
||||
not_null<Ui::VerticalLayout*> _rows;
|
||||
|
||||
QImage _userpicImage;
|
||||
QImage _barSmall;
|
||||
QImage _barLarge;
|
||||
QImage _shadowTop;
|
||||
int _smallWidth = 0;
|
||||
int _largeWidth = 0;
|
||||
int _index = 0;
|
||||
int _count = 0;
|
||||
|
||||
style::cursor _cursor = style::cur_default;
|
||||
|
||||
std::unique_ptr<Media::Streaming::Instance> _videoInstance;
|
||||
crl::time _videoStartPosition = 0;
|
||||
Fn<bool()> _videoPaused;
|
||||
QImage _shadow;
|
||||
QImage _shadowBottom;
|
||||
|
||||
rpl::event_stream<> _openRequests;
|
||||
rpl::event_stream<int> _moveRequests;
|
||||
|
||||
};
|
||||
|
@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_peer_values.h"
|
||||
#include "data/data_user_photos.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_session.h"
|
||||
#include "main/main_session.h"
|
||||
@ -29,12 +30,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kOverviewLimit = 48;
|
||||
|
||||
struct UserpicState {
|
||||
PeerShortInfoUserpic current;
|
||||
std::optional<UserPhotosSlice> userSlice;
|
||||
PhotoId userpicPhotoId = PeerData::kUnknownPhotoId;
|
||||
std::shared_ptr<Data::CloudImageView> userpicView;
|
||||
std::shared_ptr<Data::PhotoMedia> photoView;
|
||||
InMemoryKey userpicKey;
|
||||
PhotoId photoId = 0;
|
||||
PhotoId photoId = PeerData::kUnknownPhotoId;
|
||||
bool waitingFull = false;
|
||||
bool waitingLoad = false;
|
||||
};
|
||||
@ -117,8 +122,11 @@ void ProcessFullPhoto(
|
||||
} else if (const auto small = view->image(PhotoSize::Small)) {
|
||||
GenerateImage(state, small, true);
|
||||
} else {
|
||||
ProcessUserpic(peer, state);
|
||||
if (state->current.photo.isNull()) {
|
||||
const auto current = (peer->userpicPhotoId() == photo->id);
|
||||
if (current) {
|
||||
ProcessUserpic(peer, state);
|
||||
}
|
||||
if (!current || state->current.photo.isNull()) {
|
||||
if (const auto blurred = view->thumbnailInline()) {
|
||||
GenerateImage(state, blurred, true);
|
||||
}
|
||||
@ -212,43 +220,113 @@ void ProcessFullPhoto(
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::producer<PeerShortInfoUserpic> UserpicValue(
|
||||
not_null<PeerData*> peer) {
|
||||
return [=](auto consumer) {
|
||||
void ProcessOld(not_null<PeerData*> peer, not_null<UserpicState*> state) {
|
||||
|
||||
}
|
||||
|
||||
void ValidatePhotoId(
|
||||
not_null<UserpicState*> state,
|
||||
PhotoId oldUserpicPhotoId) {
|
||||
if (state->userSlice) {
|
||||
const auto count = state->userSlice->size();
|
||||
const auto hasOld = state->userSlice->indexOf(oldUserpicPhotoId);
|
||||
const auto hasNew = state->userSlice->indexOf(state->userpicPhotoId);
|
||||
const auto shift = (hasNew ? 0 : 1);
|
||||
const auto fullCount = count + shift;
|
||||
state->current.count = fullCount;
|
||||
if (hasOld && !hasNew && state->current.index + 1 < fullCount) {
|
||||
++state->current.index;
|
||||
} else if (!hasOld && hasNew && state->current.index > 0) {
|
||||
--state->current.index;
|
||||
}
|
||||
const auto index = state->current.index;
|
||||
if (!index || index >= fullCount) {
|
||||
state->current.index = 0;
|
||||
state->photoId = state->userpicPhotoId;
|
||||
} else {
|
||||
state->photoId = (*state->userSlice)[index - shift];
|
||||
}
|
||||
} else {
|
||||
state->photoId = state->userpicPhotoId;
|
||||
}
|
||||
}
|
||||
|
||||
bool ProcessCurrent(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<UserpicState*> state) {
|
||||
const auto userpicPhotoId = peer->userpicPhotoId();
|
||||
const auto userpicPhoto = (userpicPhotoId
|
||||
&& (userpicPhotoId != PeerData::kUnknownPhotoId)
|
||||
&& (state->userpicPhotoId != userpicPhotoId))
|
||||
? peer->owner().photo(userpicPhotoId).get()
|
||||
: (state->photoId == userpicPhotoId && state->photoView)
|
||||
? state->photoView->owner().get()
|
||||
: nullptr;
|
||||
state->waitingFull = (state->userpicPhotoId != userpicPhotoId)
|
||||
&& ((userpicPhotoId == PeerData::kUnknownPhotoId)
|
||||
|| (userpicPhotoId && userpicPhoto->isNull()));
|
||||
if (state->waitingFull) {
|
||||
peer->updateFullForced();
|
||||
}
|
||||
const auto oldUserpicPhotoId = state->waitingFull
|
||||
? state->userpicPhotoId
|
||||
: std::exchange(state->userpicPhotoId, userpicPhotoId);
|
||||
const auto changedUserpicPhotoId
|
||||
= (state->userpicPhotoId != oldUserpicPhotoId);
|
||||
const auto changedUserpic = (state->userpicKey
|
||||
!= peer->userpicUniqueKey(state->userpicView));
|
||||
|
||||
const auto wasIndex = state->current.index;
|
||||
const auto wasCount = state->current.count;
|
||||
const auto wasPhotoId = state->photoId;
|
||||
ValidatePhotoId(state, oldUserpicPhotoId);
|
||||
const auto changedInSlice = (state->current.index != wasIndex)
|
||||
|| (state->current.count != wasCount);
|
||||
const auto changedPhotoId = (state->photoId != wasPhotoId);
|
||||
const auto photo = (state->photoId == state->userpicPhotoId
|
||||
&& userpicPhoto)
|
||||
? userpicPhoto
|
||||
: (state->photoId
|
||||
&& (state->photoId != PeerData::kUnknownPhotoId)
|
||||
&& changedPhotoId)
|
||||
? peer->owner().photo(state->photoId).get()
|
||||
: state->photoView
|
||||
? state->photoView->owner().get()
|
||||
: nullptr;
|
||||
state->waitingLoad = false;
|
||||
if (!changedPhotoId
|
||||
&& (state->current.index > 0 || !changedUserpic)
|
||||
&& !state->photoView
|
||||
&& (!state->current.photo.isNull()
|
||||
|| state->current.videoDocument)) {
|
||||
return changedInSlice;
|
||||
} else if (photo && !photo->isNull()) {
|
||||
ProcessFullPhoto(peer, state, photo);
|
||||
} else if (state->current.index > 0) {
|
||||
return changedInSlice;
|
||||
} else {
|
||||
ProcessUserpic(peer, state);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
struct UserpicResult {
|
||||
rpl::producer<PeerShortInfoUserpic> value;
|
||||
Fn<void(int)> move;
|
||||
};
|
||||
|
||||
[[nodiscard]] UserpicResult UserpicValue(not_null<PeerData*> peer) {
|
||||
const auto moveRequests = std::make_shared<rpl::event_stream<int>>();
|
||||
auto move = [=](int shift) {
|
||||
moveRequests->fire_copy(shift);
|
||||
};
|
||||
auto value = [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
const auto state = lifetime.make_state<UserpicState>();
|
||||
const auto push = [=] {
|
||||
state->waitingLoad = false;
|
||||
const auto nowPhotoId = peer->userpicPhotoId();
|
||||
const auto photo = (nowPhotoId
|
||||
&& (nowPhotoId != PeerData::kUnknownPhotoId)
|
||||
&& (state->photoId != nowPhotoId || state->photoView))
|
||||
? peer->owner().photo(nowPhotoId).get()
|
||||
: nullptr;
|
||||
state->waitingFull = !(state->photoId == nowPhotoId)
|
||||
&& ((nowPhotoId == PeerData::kUnknownPhotoId)
|
||||
|| (nowPhotoId && photo->isNull()));
|
||||
if (state->waitingFull) {
|
||||
peer->updateFullForced();
|
||||
const auto push = [=](bool force = false) {
|
||||
if (ProcessCurrent(peer, state) || force) {
|
||||
consumer.put_next_copy(state->current);
|
||||
}
|
||||
const auto oldPhotoId = state->waitingFull
|
||||
? state->photoId
|
||||
: std::exchange(state->photoId, nowPhotoId);
|
||||
const auto changedPhotoId = (state->photoId != oldPhotoId);
|
||||
const auto changedUserpic = (state->userpicKey
|
||||
!= peer->userpicUniqueKey(state->userpicView));
|
||||
if (!changedPhotoId
|
||||
&& !changedUserpic
|
||||
&& !state->photoView
|
||||
&& (!state->current.photo.isNull()
|
||||
|| state->current.videoDocument)) {
|
||||
return;
|
||||
} else if (photo && !photo->isNull()) {
|
||||
ProcessFullPhoto(peer, state, photo);
|
||||
} else {
|
||||
ProcessUserpic(peer, state);
|
||||
}
|
||||
consumer.put_next_copy(state->current);
|
||||
};
|
||||
using UpdateFlag = Data::PeerUpdate::Flag;
|
||||
peer->session().changes().peerFlagsValue(
|
||||
@ -260,6 +338,30 @@ void ProcessFullPhoto(
|
||||
push();
|
||||
}, lifetime);
|
||||
|
||||
if (const auto user = peer->asUser()) {
|
||||
UserPhotosReversedViewer(
|
||||
&peer->session(),
|
||||
UserPhotosSlice::Key(peerToUser(user->id), PhotoId()),
|
||||
kOverviewLimit,
|
||||
kOverviewLimit
|
||||
) | rpl::start_with_next([=](UserPhotosSlice &&slice) {
|
||||
state->userSlice = std::move(slice);
|
||||
push();
|
||||
}, lifetime);
|
||||
}
|
||||
|
||||
moveRequests->events(
|
||||
) | rpl::filter([=] {
|
||||
return (state->current.count > 1);
|
||||
}) | rpl::start_with_next([=](int shift) {
|
||||
state->current.index = std::clamp(
|
||||
((state->current.index + shift + state->current.count)
|
||||
% state->current.count),
|
||||
0,
|
||||
state->current.count - 1);
|
||||
push(true);
|
||||
}, lifetime);
|
||||
|
||||
peer->session().downloaderTaskFinished(
|
||||
) | rpl::filter([=] {
|
||||
return state->waitingLoad
|
||||
@ -272,6 +374,7 @@ void ProcessFullPhoto(
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
return { .value = std::move(value), .move = std::move(move) };
|
||||
}
|
||||
|
||||
object_ptr<Ui::BoxContent> PrepareShortInfoBox(
|
||||
@ -283,16 +386,20 @@ object_ptr<Ui::BoxContent> PrepareShortInfoBox(
|
||||
: peer->isBroadcast()
|
||||
? PeerShortInfoType::Channel
|
||||
: PeerShortInfoType::Group;
|
||||
auto userpic = UserpicValue(peer);
|
||||
auto result = Box<PeerShortInfoBox>(
|
||||
type,
|
||||
FieldsValue(peer),
|
||||
StatusValue(peer),
|
||||
UserpicValue(peer),
|
||||
std::move(userpic.value),
|
||||
std::move(videoPaused));
|
||||
|
||||
result->openRequests(
|
||||
) | rpl::start_with_next(open, result->lifetime());
|
||||
|
||||
result->moveRequests(
|
||||
) | rpl::start_with_next(userpic.move, result->lifetime());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -971,3 +971,6 @@ shortInfoNamePosition: point(25px, 37px);
|
||||
shortInfoStatusPosition: point(25px, 14px);
|
||||
shortInfoShadowHeight: 80px;
|
||||
shortInfoLabeledPadding: margins(20px, 16px, 20px, 0px);
|
||||
shortInfoLinePadding: 8px;
|
||||
shortInfoLineSkip: 4px;
|
||||
shortInfoLine: 2px;
|
||||
|
Loading…
Reference in New Issue
Block a user