diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index 07ed20f701..02ecf89ba7 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -1746,3 +1746,5 @@ menuTranscribeDummyButton: IconButton(defaultIconButton) { } roundVideoFont: font(14px semibold); + +topPeersSelectorUserpicExpand: 0.1; diff --git a/Telegram/SourceFiles/ui/controls/dynamic_images_strip.cpp b/Telegram/SourceFiles/ui/controls/dynamic_images_strip.cpp new file mode 100644 index 0000000000..538b10f85e --- /dev/null +++ b/Telegram/SourceFiles/ui/controls/dynamic_images_strip.cpp @@ -0,0 +1,160 @@ +// 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/controls/dynamic_images_strip.h" + +#include "ui/dynamic_image.h" +#include "ui/painter.h" +#include "styles/style_chat_helpers.h" + +namespace Ui { +namespace { + +constexpr auto kDimmedAlpha = 0.6; +constexpr auto kHoverScaleIncrease = st::topPeersSelectorUserpicExpand; + +} // namespace + +DynamicImagesStrip::DynamicImagesStrip( + QWidget *parent, + std::vector> thumbnails, + int userpicSize, + int gap) +: RpWidget(parent) +, _thumbnails(std::move(thumbnails)) +, _userpicSize(userpicSize) +, _gap(gap) +, _scales(_thumbnails.size(), 0.) +, _alphas(_thumbnails.size(), 1.) +, _scaleTargets(_thumbnails.size(), 0.) +, _alphaTargets(_thumbnails.size(), 1.) +, _animation([=](crl::time now) { + const auto dt = (now - _animation.started()) + / float64(st::universalDuration); + auto finished = true; + for (auto i = 0; i < int(_thumbnails.size()); ++i) { + const auto progress = std::clamp(dt, 0., 1.); + _scales[i] += (_scaleTargets[i] - _scales[i]) * progress; + _alphas[i] += (_alphaTargets[i] - _alphas[i]) * progress; + if (std::abs(_scales[i] - _scaleTargets[i]) > 0.01 + || std::abs(_alphas[i] - _alphaTargets[i]) > 0.01) { + finished = false; + } + } + update(); + return !finished; +}) { + for (const auto &thumbnail : _thumbnails) { + thumbnail->subscribeToUpdates([=] { update(); }); + } + setMouseTracking(true); +} + +void DynamicImagesStrip::setProgress(float64 progress) { + _progress = progress; + update(); +} + +void DynamicImagesStrip::setClickCallback(Fn callback) { + _clickCallback = std::move(callback); +} + +void DynamicImagesStrip::paintEvent(QPaintEvent *e) { + auto p = QPainter(this); + { + const auto shift = (height() - _userpicSize) / 2; + p.translate(shift, shift); + } + auto x = 0; + const auto count = int(_thumbnails.size()); + const auto duration = 0.25; + const auto overlap = 0.15; + auto hq = PainterHighQualityEnabler(p); + for (auto i = 0; i < count; ++i) { + const auto center = count / 2; + const auto offset = (i <= center) + ? (center - i) + : (i - center); + const auto start = offset * (duration - overlap); + const auto scale = std::clamp( + (_progress - start) / duration, + 0., + 1.); + if (scale > 0.) { + const auto hoverScale = 1. + kHoverScaleIncrease * _scales[i]; + const auto alpha = _alphas[i]; + const auto cx = x + _userpicSize / 2; + const auto cy = height() / 2; + p.save(); + p.setOpacity(alpha); + p.translate(cx, cy); + p.scale(scale * hoverScale, scale * hoverScale); + p.translate(-cx, -cy); + const auto image = _thumbnails[i]->image(_userpicSize); + p.drawImage(x, 0, image); + p.restore(); + } + x += _userpicSize + _gap; + } +} + +void DynamicImagesStrip::mouseMoveEvent(QMouseEvent *e) { + const auto pos = e->pos().x(); + const auto step = _userpicSize + _gap; + const auto count = int(_thumbnails.size()); + auto newIndex = -1; + for (auto i = 0; i < count; ++i) { + const auto start = i * step; + const auto end = start + _userpicSize + _gap / 2; + if (pos >= start && pos < end) { + newIndex = i; + break; + } + } + if (_hoveredIndex != newIndex) { + const auto prev = _hoveredIndex; + _hoveredIndex = newIndex; + if (prev >= 0) { + _scaleTargets[prev] = 0.; + _alphaTargets[prev] = kDimmedAlpha; + } + if (newIndex >= 0) { + _scaleTargets[newIndex] = 1.; + _alphaTargets[newIndex] = 1.; + for (auto i = 0; i < count; ++i) { + if (i != newIndex && _alphaTargets[i] > kDimmedAlpha) { + _alphaTargets[i] = kDimmedAlpha; + } + } + } + startAnimation(); + } +} + +void DynamicImagesStrip::mousePressEvent(QMouseEvent *e) { + if (_hoveredIndex >= 0 && _clickCallback) { + _clickCallback(_hoveredIndex); + } +} + +void DynamicImagesStrip::leaveEventHook(QEvent *e) { + if (_hoveredIndex >= 0) { + _hoveredIndex = -1; + for (auto i = 0; i < int(_thumbnails.size()); ++i) { + _scaleTargets[i] = 0.; + _alphaTargets[i] = 1.; + } + startAnimation(); + } +} + +void DynamicImagesStrip::startAnimation() { + if (!_animation.animating()) { + _animation.start(); + } +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/controls/dynamic_images_strip.h b/Telegram/SourceFiles/ui/controls/dynamic_images_strip.h new file mode 100644 index 0000000000..49d6fe53b7 --- /dev/null +++ b/Telegram/SourceFiles/ui/controls/dynamic_images_strip.h @@ -0,0 +1,50 @@ +// 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 + +#include "ui/rp_widget.h" +#include "ui/effects/animations.h" + +namespace Ui { + +class DynamicImage; + +class DynamicImagesStrip final : public RpWidget { +public: + DynamicImagesStrip( + QWidget *parent, + std::vector> thumbnails, + int userpicSize, + int gap); + + void setProgress(float64 progress); + void setClickCallback(Fn callback); + +protected: + void paintEvent(QPaintEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + void leaveEventHook(QEvent *e) override; + +private: + void startAnimation(); + + std::vector> _thumbnails; + int _userpicSize = 0; + int _gap = 0; + float64 _progress = 0.; + int _hoveredIndex = -1; + std::vector _scales; + std::vector _alphas; + std::vector _scaleTargets; + std::vector _alphaTargets; + Animations::Basic _animation; + Fn _clickCallback; + +}; + +} // namespace Ui diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 6227d3d0f9..32598631f5 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -403,6 +403,8 @@ PRIVATE ui/controls/delete_message_context_action.h ui/controls/download_bar.cpp ui/controls/download_bar.h + ui/controls/dynamic_images_strip.cpp + ui/controls/dynamic_images_strip.h ui/controls/emoji_button.cpp ui/controls/emoji_button.h ui/controls/feature_list.cpp