Display group / userpic thumbnails in MediaView.

This commit is contained in:
John Preston 2017-12-28 17:58:34 +03:00
parent 5b4694a4eb
commit 2bcbb5a5be
5 changed files with 835 additions and 75 deletions

View File

@ -20,23 +20,613 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "media/view/media_view_group_thumbs.h"
#include "data/data_shared_media.h"
#include "data/data_user_photos.h"
#include "data/data_photo.h"
#include "data/data_document.h"
#include "history/history_media.h"
#include "styles/style_mediaview.h"
namespace Media {
namespace View {
namespace {
GroupThumbs::GroupThumbs() {
constexpr auto kThumbDuration = TimeMs(150);
int Round(float64 value) {
return int(std::round(value));
}
using Context = GroupThumbs::Context;
using Key = GroupThumbs::Key;
Context ComputeContext(const SharedMediaWithLastSlice &slice, int index) {
Expects(index >= 0 && index < slice.size());
const auto value = slice[index];
if (const auto photo = base::get_if<not_null<PhotoData*>>(&value)) {
if (const auto peer = (*photo)->peer) {
return peer->id;
}
return base::none;
} else if (const auto msgId = base::get_if<FullMsgId>(&value)) {
if (const auto item = App::histItemById(*msgId)) {
if (!item->toHistoryMessage()) {
return item->history()->peer->id;
} else if (const auto groupId = item->groupId()) {
return groupId;
}
}
return base::none;
}
Unexpected("Variant in ComputeContext(SharedMediaWithLastSlice::Value)");
}
Context ComputeContext(const UserPhotosSlice &slice, int index) {
return peerFromUser(slice.key().userId);
}
Key ComputeKey(const SharedMediaWithLastSlice &slice, int index) {
Expects(index >= 0 && index < slice.size());
const auto value = slice[index];
if (const auto photo = base::get_if<not_null<PhotoData*>>(&value)) {
return (*photo)->id;
} else if (const auto msgId = base::get_if<FullMsgId>(&value)) {
return *msgId;
}
Unexpected("Variant in ComputeContext(SharedMediaWithLastSlice::Value)");
}
Key ComputeKey(const UserPhotosSlice &slice, int index) {
return slice[index];
}
int ComputeThumbsLimit(int availableWidth) {
const auto singleWidth = st::mediaviewGroupWidth
+ 2 * st::mediaviewGroupSkip;
const auto currentWidth = st::mediaviewGroupWidthMax
+ 2 * st::mediaviewGroupSkipCurrent;
const auto skipForAnimation = 2 * singleWidth;
const auto leftWidth = availableWidth
- currentWidth
- skipForAnimation;
return std::max(leftWidth / (2 * singleWidth), 1);
}
} // namespace
class GroupThumbs::Thumb {
public:
enum class State {
Unknown,
Current,
Alive,
Dying,
};
Thumb(Key key, ImagePtr image);
int leftToUpdate() const;
int rightToUpdate() const;
void animateToLeft(not_null<Thumb*> next);
void animateToRight(not_null<Thumb*> prev);
void setState(State state);
State state() const;
bool removed() const;
void paint(Painter &p, int x, int y, int outerWidth, float64 progress);
private:
QSize wantedPixSize() const;
void validateImage();
int currentLeft() const;
int currentWidth() const;
int finalLeft() const;
int finalWidth() const;
void toggle(bool visible);
void animateTo(int left, int width);
const Key _key;
ImagePtr _image;
State _state = State::Alive;
QPixmap _full;
int _fullWidth = 0;
bool _hiding = true;
anim::value _left = { 0. };
anim::value _width = { 0. };
anim::value _opacity = { 0. };
};
GroupThumbs::Thumb::Thumb(Key key, ImagePtr image)
: _key(key)
, _image(image) {
_fullWidth = std::min(
wantedPixSize().width(),
st::mediaviewGroupWidthMax);
validateImage();
toggle(true);
}
QSize GroupThumbs::Thumb::wantedPixSize() const {
const auto originalWidth = std::max(_image->width(), 1);
const auto originalHeight = std::max(_image->height(), 1);
const auto pixHeight = st::mediaviewGroupHeight;
const auto pixWidth = originalWidth * pixHeight / originalHeight;
return { pixWidth, pixHeight };
}
void GroupThumbs::Thumb::validateImage() {
if (!_full.isNull()) {
return;
}
_image->load();
if (!_image->loaded()) {
return;
}
const auto pixSize = wantedPixSize();
if (pixSize.width() > st::mediaviewGroupWidthMax) {
const auto originalWidth = _image->width();
const auto originalHeight = _image->height();
const auto takeWidth = originalWidth * st::mediaviewGroupWidthMax
/ pixSize.width();
const auto original = _image->pixNoCache().toImage();
_full = App::pixmapFromImageInPlace(original.copy(
(originalWidth - takeWidth) / 2,
0,
takeWidth,
originalHeight
).scaled(
st::mediaviewGroupWidthMax * cIntRetinaFactor(),
pixSize.height() * cIntRetinaFactor(),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation));
} else {
_full = _image->pixNoCache(
pixSize.width() * cIntRetinaFactor(),
pixSize.height() * cIntRetinaFactor(),
Images::Option::Smooth);
}
}
int GroupThumbs::Thumb::leftToUpdate() const {
return Round(std::min(_left.from(), _left.to()));
}
int GroupThumbs::Thumb::rightToUpdate() const {
return Round(std::max(
_left.from() + _width.from(),
_left.to() + _width.to()));
}
int GroupThumbs::Thumb::currentLeft() const {
return Round(_left.current());
}
int GroupThumbs::Thumb::currentWidth() const {
return Round(_width.current());
}
int GroupThumbs::Thumb::finalLeft() const {
return Round(_left.to());
}
int GroupThumbs::Thumb::finalWidth() const {
return Round(_width.to());
}
void GroupThumbs::Thumb::setState(State state) {
const auto isNewThumb = (_state == State::Alive);
_state = state;
if (_state == State::Current) {
if (isNewThumb) {
_opacity = anim::value(1.);
_left = anim::value(-_fullWidth / 2);
_width = anim::value(_fullWidth);
} else {
toggle(true);
}
animateTo(-_fullWidth / 2, _fullWidth);
} else if (_state == State::Alive) {
toggle(true);
} else if (_state == State::Dying) {
toggle(false);
_left.restart();
_width.restart();
}
}
void GroupThumbs::Thumb::toggle(bool visible) {
_hiding = !visible;
_opacity.start(_hiding ? 0. : 1.);
}
void GroupThumbs::Thumb::animateTo(int left, int width) {
_left.start(left);
_width.start(width);
}
void GroupThumbs::Thumb::animateToLeft(not_null<Thumb*> next) {
const auto width = st::mediaviewGroupWidth;
if (_state == State::Alive) {
// New item animation, start exactly from the next, move only.
_left = anim::value(next->currentLeft() - width);
_width = anim::value(width);
} else if (_state == State::Unknown) {
// Existing item animation.
setState(State::Alive);
}
const auto skip1 = st::mediaviewGroupSkip;
const auto skip2 = (next->state() == State::Current)
? st::mediaviewGroupSkipCurrent
: st::mediaviewGroupSkip;
animateTo(next->finalLeft() - width - skip1 - skip2, width);
}
void GroupThumbs::Thumb::animateToRight(not_null<Thumb*> prev) {
const auto width = st::mediaviewGroupWidth;
if (_state == State::Alive) {
// New item animation, start exactly from the next, move only.
_left = anim::value(prev->currentLeft() + prev->currentWidth());
_width = anim::value(width);
} else if (_state == State::Unknown) {
// Existing item animation.
setState(State::Alive);
}
const auto skip1 = st::mediaviewGroupSkip;
const auto skip2 = (prev->state() == State::Current)
? st::mediaviewGroupSkipCurrent
: st::mediaviewGroupSkip;
animateTo(prev->finalLeft() + prev->finalWidth() + skip1 + skip2, width);
}
auto GroupThumbs::Thumb::state() const -> State {
return _state;
}
bool GroupThumbs::Thumb::removed() const {
return (_state == State::Dying) && _hiding && !_opacity.current();
}
void GroupThumbs::Thumb::paint(
Painter &p,
int x,
int y,
int outerWidth,
float64 progress) {
validateImage();
_opacity.update(progress, anim::linear);
_left.update(progress, anim::linear);
_width.update(progress, anim::linear);
const auto left = x + currentLeft();
const auto width = currentWidth();
const auto opacity = p.opacity();
p.setOpacity(_opacity.current() * opacity);
if (width == _fullWidth) {
p.drawPixmap(left, y, _full);
} else {
const auto takeWidth = width * cIntRetinaFactor();
const auto from = QRect(
(_full.width() - takeWidth) / 2,
0,
takeWidth,
_full.height());
const auto to = QRect(left, y, width, st::mediaviewGroupHeight);
p.drawPixmap(to, _full, from);
}
p.setOpacity(opacity);
}
GroupThumbs::GroupThumbs(Context context)
: _context(context) {
}
void GroupThumbs::updateContext(Context context) {
if (_context != context) {
clear();
_context = context;
}
}
template <typename Slice>
void GroupThumbs::RefreshFromSlice(
std::unique_ptr<GroupThumbs> &instance,
const Slice &slice,
int index,
int availableWidth) {
const auto context = ComputeContext(slice, index);
if (instance) {
instance->updateContext(context);
}
if (!context) {
if (instance) {
instance->resizeToWidth(availableWidth);
}
return;
}
const auto limit = ComputeThumbsLimit(availableWidth);
const auto from = [&] {
const auto edge = std::max(index - limit, 0);
for (auto result = index; result != edge; --result) {
if (ComputeContext(slice, result - 1) != context) {
return result;
}
}
return edge;
}();
const auto till = [&] {
const auto edge = std::min(index + limit + 1, slice.size());
for (auto result = index + 1; result != edge; ++result) {
if (ComputeContext(slice, result) != context) {
return result;
}
}
return edge;
}();
if (from + 1 < till) {
if (!instance) {
instance = std::make_unique<GroupThumbs>(context);
}
instance->fillItems(slice, from, index, till);
instance->resizeToWidth(availableWidth);
} else if (instance) {
instance->clear();
instance->resizeToWidth(availableWidth);
}
}
template <typename Slice>
void GroupThumbs::fillItems(
const Slice &slice,
int from,
int index,
int till) {
Expects(from <= index);
Expects(index < till);
Expects(from + 1 < till);
const auto current = (index - from);
const auto old = base::take(_items);
markCacheStale();
_items.reserve(till - from);
for (auto i = from; i != till; ++i) {
_items.push_back(validateCacheEntry(ComputeKey(slice, i)));
}
animateAliveItems(current);
fillDyingItems(old);
startDelayedAnimation();
}
void GroupThumbs::animateAliveItems(int current) {
Expects(current >= 0 && current < _items.size());
_items[current]->setState(Thumb::State::Current);
for (auto i = current; i != 0;) {
const auto prev = _items[i];
const auto item = _items[--i];
item->animateToLeft(prev);
}
for (auto i = current + 1; i != _items.size(); ++i) {
const auto prev = _items[i - 1];
const auto item = _items[i];
item->animateToRight(prev);
}
}
void GroupThumbs::fillDyingItems(const std::vector<not_null<Thumb*>> &old) {
_dying.reserve(_cache.size() - _items.size());
animatePreviouslyAlive(old);
markRestAsDying();
}
void GroupThumbs::markRestAsDying() {
_dying.reserve(_cache.size() - _items.size());
for (const auto &[key, thumb] : _cache) {
const auto state = thumb->state();
if (state == Thumb::State::Unknown) {
markAsDying(thumb.get());
}
}
}
void GroupThumbs::markAsDying(not_null<Thumb*> thumb) {
thumb->setState(Thumb::State::Dying);
_dying.push_back(thumb.get());
}
void GroupThumbs::animatePreviouslyAlive(
const std::vector<not_null<Thumb*>> &old) {
auto toRight = false;
for (auto i = 0; i != old.size(); ++i) {
const auto item = old[i];
if (item->state() == Thumb::State::Unknown) {
if (toRight) {
markAsDying(item);
item->animateToRight(old[i - 1]);
}
} else if (!toRight) {
for (auto j = i; j != 0;) {
const auto next = old[j];
const auto prev = old[--j];
markAsDying(prev);
prev->animateToLeft(next);
}
toRight = true;
}
}
}
auto GroupThumbs::createThumb(Key key) -> std::unique_ptr<Thumb> {
if (const auto photoId = base::get_if<PhotoId>(&key)) {
const auto photo = App::photo(*photoId);
return createThumb(key, photo->date ? photo->thumb : ImagePtr());
} else if (const auto msgId = base::get_if<FullMsgId>(&key)) {
if (const auto item = App::histItemById(*msgId)) {
if (const auto media = item->getMedia()) {
if (const auto photo = media->getPhoto()) {
return createThumb(key, photo->thumb);
} else if (const auto document = media->getDocument()) {
return createThumb(key, document->thumb);
}
}
}
return createThumb(key, ImagePtr());
}
Unexpected("Value of Key in GroupThumbs::createThumb()");
}
auto GroupThumbs::createThumb(Key key, ImagePtr image)
-> std::unique_ptr<Thumb> {
return std::make_unique<Thumb>(key, image);
}
auto GroupThumbs::validateCacheEntry(Key key) -> not_null<Thumb*> {
const auto i = _cache.find(key);
return (i != _cache.end())
? i->second.get()
: _cache.emplace(key, createThumb(key)).first->second.get();
}
void GroupThumbs::markCacheStale() {
while (!_dying.empty()) {
_dying.pop_back();
}
for (const auto &[key, thumb] : _cache) {
thumb->setState(Thumb::State::Unknown);
}
}
void GroupThumbs::Refresh(
std::unique_ptr<GroupThumbs> &instance,
const SharedMediaWithLastSlice &slice,
int index,
int availableWidth) {
RefreshFromSlice(instance, slice, index, availableWidth);
}
void GroupThumbs::Refresh(
std::unique_ptr<GroupThumbs> &instance,
const UserPhotosSlice &slice,
int index,
int availableWidth) {
RefreshFromSlice(instance, slice, index, availableWidth);
}
void GroupThumbs::clear() {
if (_items.empty()) {
return;
}
base::take(_items);
markCacheStale();
markRestAsDying();
startDelayedAnimation();
}
void GroupThumbs::startDelayedAnimation() {
_animation.finish();
_waitingForAnimationStart = true;
countUpdatedRect();
}
void GroupThumbs::resizeToWidth(int newWidth) {
_width = newWidth;
}
int GroupThumbs::height() const {
return 0;
return st::mediaviewGroupPadding.top()
+ st::mediaviewGroupHeight
+ st::mediaviewGroupPadding.bottom();
}
QRect GroupThumbs::paintedRect() const {
return QRect();
bool GroupThumbs::hiding() const {
return _items.empty();
}
bool GroupThumbs::hidden() const {
return hiding() && !_waitingForAnimationStart && !_animation.animating();
}
void GroupThumbs::checkForAnimationStart() {
if (_waitingForAnimationStart) {
_waitingForAnimationStart = false;
_animation.start([this] { update(); }, 0., 1., kThumbDuration);
}
}
void GroupThumbs::update() {
if (_cache.empty()) {
return;
}
_updateRequests.fire_copy(_updatedRect);
}
void GroupThumbs::paint(
Painter &p,
int x,
int y,
int outerWidth,
TimeMs ms) {
const auto progress = _waitingForAnimationStart
? 0.
: _animation.current(ms, 1.);
x += (_width / 2);
y += st::mediaviewGroupPadding.top();
for (auto i = _cache.begin(); i != _cache.end();) {
const auto &thumb = i->second;
thumb->paint(p, x, y, outerWidth, progress);
if (thumb->removed()) {
_dying.erase(
ranges::remove(
_dying,
thumb.get(),
[](not_null<Thumb*> thumb) { return thumb.get(); }),
_dying.end());
i = _cache.erase(i);
} else {
++i;
}
}
}
void GroupThumbs::countUpdatedRect() {
if (_cache.empty()) {
return;
}
auto min = _width;
auto max = 0;
const auto left = [](const auto &cacheItem) {
const auto &[key, thumb] = cacheItem;
return thumb->leftToUpdate();
};
const auto right = [](const auto &cacheItem) {
const auto &[key, thumb] = cacheItem;
return thumb->rightToUpdate();
};
accumulate_min(min, left(*ranges::max_element(
_cache,
std::greater<>(),
left)));
accumulate_max(max, right(*ranges::max_element(
_cache,
std::less<>(),
right)));
_updatedRect = QRect(
min,
st::mediaviewGroupPadding.top(),
max - min,
st::mediaviewGroupHeight);
}
GroupThumbs::~GroupThumbs() = default;
} // namespace View
} // namespace Media

View File

@ -20,19 +20,88 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "history/history_item_components.h"
class SharedMediaWithLastSlice;
class UserPhotosSlice;
namespace Media {
namespace View {
class GroupThumbs {
public:
GroupThumbs();
static void Refresh(
std::unique_ptr<GroupThumbs> &instance,
const SharedMediaWithLastSlice &slice,
int index,
int availableWidth);
static void Refresh(
std::unique_ptr<GroupThumbs> &instance,
const UserPhotosSlice &slice,
int index,
int availableWidth);
void clear();
void resizeToWidth(int newWidth);
int height() const;
bool hiding() const;
bool hidden() const;
void checkForAnimationStart();
QRect paintedRect() const;
void paint(Painter &p, int x, int y, int outerWidth, TimeMs ms);
rpl::producer<QRect> updateRequests() const {
return _updateRequests.events();
}
rpl::lifetime &lifetime() {
return _lifetime;
}
using Context = base::optional_variant<PeerId, MessageGroupId>;
using Key = base::variant<PhotoId, FullMsgId>;
GroupThumbs(Context context);
~GroupThumbs();
private:
class Thumb;
template <typename Slice>
static void RefreshFromSlice(
std::unique_ptr<GroupThumbs> &instance,
const Slice &slice,
int index,
int availableWidth);
template <typename Slice>
void fillItems(const Slice &slice, int from, int index, int till);
void updateContext(Context context);
void markCacheStale();
not_null<Thumb*> validateCacheEntry(Key key);
std::unique_ptr<Thumb> createThumb(Key key);
std::unique_ptr<Thumb> createThumb(Key key, ImagePtr image);
void update();
void countUpdatedRect();
void animateAliveItems(int current);
void fillDyingItems(const std::vector<not_null<Thumb*>> &old);
void markAsDying(not_null<Thumb*> thumb);
void markRestAsDying();
void animatePreviouslyAlive(const std::vector<not_null<Thumb*>> &old);
void startDelayedAnimation();
Context _context;
bool _waitingForAnimationStart = true;
Animation _animation;
std::vector<not_null<Thumb*>> _items;
std::vector<not_null<Thumb*>> _dying;
base::flat_map<Key, std::unique_ptr<Thumb>> _cache;
int _width = 0;
int _limit = 0;
rpl::event_stream<QRect> _updateRequests;
rpl::lifetime _lifetime;
QRect _updatedRect;
};

View File

@ -196,6 +196,13 @@ mediaviewCaptionPadding: margins(18px, 10px, 18px, 10px);
mediaviewCaptionMargin: size(11px, 11px);
mediaviewCaptionRadius: 2px;
mediaviewGroupPadding: margins(0px, 14px, 0px, 14px);
mediaviewGroupHeight: 80px;
mediaviewGroupWidth: 56px;
mediaviewGroupWidthMax: 160px;
mediaviewGroupSkip: 3px;
mediaviewGroupSkipCurrent: 12px;
themePreviewSize: size(903px, 584px);
themePreviewBg: windowBg;
themePreviewOverlayOpacity: 0.8;

View File

@ -369,45 +369,54 @@ void MediaView::updateControls() {
void MediaView::resizeCenteredControls() {
const auto bottomSkip = std::max(
_dateNav.left() + _dateNav.width(),
_headerNav.left() + _headerNav.width());
const auto bottomWidth = std::max(
width() - 2 * bottomSkip - 2 * st::mediaviewCaptionMargin.width(),
_headerNav.left() + _headerNav.width())
+ st::mediaviewCaptionMargin.width();
_groupThumbsAvailableWidth = std::max(
width() - 2 * bottomSkip,
st::msgMinWidth
+ st::mediaviewCaptionPadding.left()
+ st::mediaviewCaptionPadding.right());
if (_groupThumbs) {
_groupThumbs->resizeToWidth(bottomWidth);
_groupThumbsTop = height() - _groupThumbs->height();
_groupThumbsLeft = (width() - _groupThumbsAvailableWidth) / 2;
refreshGroupThumbs();
_groupThumbsTop = _groupThumbs ? (height() - _groupThumbs->height()) : 0;
refreshClipControllerGeometry();
refreshCaptionGeometry();
}
void MediaView::refreshCaptionGeometry() {
if (_caption.isEmpty()) {
_captionRect = QRect();
return;
}
if (!_caption.isEmpty()) {
const auto captionBottom = groupThumbsDisplayed()
? _groupThumbsTop
: height() - st::mediaviewCaptionMargin.height();
const auto captionWidth = std::min(
bottomWidth
- st::mediaviewCaptionPadding.left()
- st::mediaviewCaptionPadding.right(),
_caption.maxWidth());
const auto captionHeight = std::min(
_caption.countHeight(captionWidth),
height() / 4
- st::mediaviewCaptionPadding.top()
- st::mediaviewCaptionPadding.bottom()
- 2 * st::mediaviewCaptionMargin.height());
_captionRect = QRect(
(width() - captionWidth) / 2,
captionBottom
- captionHeight
- st::mediaviewCaptionPadding.bottom(),
captionWidth,
captionHeight);
} else {
_captionRect = QRect();
}
if (_clipController) {
setClipControllerGeometry();
if (_groupThumbs && _groupThumbs->hiding()) {
_groupThumbs = nullptr;
_groupThumbsRect = QRect();
}
const auto captionBottom = _clipController
? (_clipController->y() - st::mediaviewCaptionMargin.height())
: _groupThumbs
? _groupThumbsTop
: height() - st::mediaviewCaptionMargin.height();
const auto captionWidth = std::min(
_groupThumbsAvailableWidth
- st::mediaviewCaptionPadding.left()
- st::mediaviewCaptionPadding.right(),
_caption.maxWidth());
const auto captionHeight = std::min(
_caption.countHeight(captionWidth),
height() / 4
- st::mediaviewCaptionPadding.top()
- st::mediaviewCaptionPadding.bottom()
- 2 * st::mediaviewCaptionMargin.height());
_captionRect = QRect(
(width() - captionWidth) / 2,
captionBottom
- captionHeight
- st::mediaviewCaptionPadding.bottom(),
captionWidth,
captionHeight);
}
void MediaView::updateActions() {
@ -502,7 +511,17 @@ void MediaView::step_state(TimeMs ms, bool timer) {
} else {
a_cOpacity.update(dt, anim::linear);
}
QRegion toUpdate = QRegion() + (_over == OverLeftNav ? _leftNav : _leftNavIcon) + (_over == OverRightNav ? _rightNav : _rightNavIcon) + (_over == OverClose ? _closeNav : _closeNavIcon) + _saveNavIcon + _moreNavIcon + _headerNav + _nameNav + _dateNav + _captionRect.marginsAdded(st::mediaviewCaptionPadding);
const auto toUpdate = QRegion()
+ (_over == OverLeftNav ? _leftNav : _leftNavIcon)
+ (_over == OverRightNav ? _rightNav : _rightNavIcon)
+ (_over == OverClose ? _closeNav : _closeNavIcon)
+ _saveNavIcon
+ _moreNavIcon
+ _headerNav
+ _nameNav
+ _dateNav
+ _captionRect.marginsAdded(st::mediaviewCaptionPadding)
+ _groupThumbsRect;
update(toUpdate);
if (dt < 1) result = true;
}
@ -512,7 +531,9 @@ void MediaView::step_state(TimeMs ms, bool timer) {
}
void MediaView::updateCursor() {
setCursor(_controlsState == ControlsHidden ? Qt::BlankCursor : (_over == OverNone ? style::cur_default : style::cur_pointer));
setCursor(_controlsState == ControlsHidden
? Qt::BlankCursor
: (_over == OverNone ? style::cur_default : style::cur_pointer));
}
float64 MediaView::radialProgress() const {
@ -566,8 +587,8 @@ void MediaView::step_radial(TimeMs ms, bool timer) {
update(radialRect());
}
const auto ready = _doc && _doc->loaded();
const auto streamVideo = _doc->isAnimation() || _doc->isVideoFile();
const auto tryOpenImage = (_doc->size < App::kImageSizeLimit);
const auto streamVideo = ready && (_doc->isAnimation() || _doc->isVideoFile());
const auto tryOpenImage = ready && (_doc->size < App::kImageSizeLimit);
if (ready && ((tryOpenImage && !_radial.animating()) || streamVideo)) {
if (_doc->isVideoFile() || _doc->isVideoMessage()) {
_autoplayVideoDocument = _doc;
@ -737,6 +758,7 @@ void MediaView::onHideControls(bool force) {
}
if (_controlsState == ControlsHiding || _controlsState == ControlsHidden) return;
_lastMouseMovePos = mapFromGlobal(QCursor::pos());
_controlsState = ControlsHiding;
_controlsAnimStarted = getms();
a_cOpacity.start(0);
@ -1247,6 +1269,43 @@ void MediaView::refreshCaption(HistoryItem *item) {
Ui::ItemTextOptions(item));
}
void MediaView::refreshGroupThumbs() {
const auto existed = (_groupThumbs != nullptr);
if (_index && _sharedMediaData) {
Media::View::GroupThumbs::Refresh(
_groupThumbs,
*_sharedMediaData,
*_index,
_groupThumbsAvailableWidth);
} else if (_index && _userPhotosData) {
Media::View::GroupThumbs::Refresh(
_groupThumbs,
*_userPhotosData,
*_index,
_groupThumbsAvailableWidth);
} else if (_groupThumbs) {
_groupThumbs->clear();
_groupThumbs->resizeToWidth(_groupThumbsAvailableWidth);
}
if (_groupThumbs && !existed) {
_groupThumbs->updateRequests(
) | rpl::start_with_next([this](QRect rect) {
const auto shift = (width() / 2);
_groupThumbsRect = QRect(
shift + rect.x(),
_groupThumbsTop,
rect.width(),
_groupThumbs->height());
update(_groupThumbsRect);
}, _groupThumbs->lifetime());
_groupThumbsRect = QRect(
_groupThumbsLeft,
_groupThumbsTop,
width() - 2 * _groupThumbsLeft,
height() - _groupThumbsTop);
}
}
void MediaView::showPhoto(not_null<PhotoData*> photo, HistoryItem *context) {
if (context) {
setContext(context);
@ -1403,7 +1462,9 @@ void MediaView::displayDocument(DocumentData *doc, HistoryItem *item) { // empty
_autoplayVideoDocument = nullptr;
}
_caption = Text();
if (documentChanged) {
refreshCaption(item);
}
if (_doc) {
if (_doc->sticker()) {
_doc->checkSticker();
@ -1673,7 +1734,7 @@ void MediaView::createClipController() {
if (!_doc->isVideoFile() && !_doc->isVideoMessage()) return;
_clipController.create(this);
setClipControllerGeometry();
refreshClipControllerGeometry();
_clipController->show();
connect(_clipController, SIGNAL(playPressed()), this, SLOT(onVideoPauseResume()));
@ -1687,10 +1748,18 @@ void MediaView::createClipController() {
connect(Media::Player::mixer(), SIGNAL(updated(const AudioMsgId&)), this, SLOT(onVideoPlayProgress(const AudioMsgId&)));
}
void MediaView::setClipControllerGeometry() {
Assert(_clipController != nullptr);
void MediaView::refreshClipControllerGeometry() {
if (!_clipController) {
return;
}
int controllerBottom = _captionRect.isEmpty() ? height() : _captionRect.y();
if (_groupThumbs && _groupThumbs->hiding()) {
_groupThumbs = nullptr;
_groupThumbsRect = QRect();
}
const auto controllerBottom = _groupThumbs
? _groupThumbsTop
: height();
_clipController->setGeometry(
(width() - _clipController->width()) / 2,
controllerBottom - _clipController->height() - st::mediaviewCaptionPadding.bottom() - st::mediaviewCaptionMargin.height(),
@ -2111,6 +2180,27 @@ void MediaView::paintEvent(QPaintEvent *e) {
}
}
}
if (_groupThumbs && _groupThumbsRect.intersects(r)) {
p.setOpacity(co);
_groupThumbs->paint(
p,
_groupThumbsLeft,
_groupThumbsTop,
width(),
ms);
if (_groupThumbs->hidden()) {
_groupThumbs = nullptr;
_groupThumbsRect = QRect();
}
}
}
checkGroupThumbsAnimation();
}
void MediaView::checkGroupThumbsAnimation() {
if (_groupThumbs && (!_gif || _gif->started())) {
_groupThumbs->checkForAnimationStart();
}
}
@ -2223,8 +2313,14 @@ void MediaView::keyPressEvent(QKeyEvent *e) {
onVideoPauseResume();
}
} else if (e->key() == Qt::Key_Left) {
if (_controlsHideTimer.isActive()) {
activateControls();
}
moveToNext(-1);
} else if (e->key() == Qt::Key_Right) {
if (_controlsHideTimer.isActive()) {
activateControls();
}
moveToNext(1);
} else if (e->modifiers().testFlag(Qt::ControlModifier) && (e->key() == Qt::Key_Plus || e->key() == Qt::Key_Equal || e->key() == ']' || e->key() == Qt::Key_Asterisk || e->key() == Qt::Key_Minus || e->key() == Qt::Key_Underscore || e->key() == Qt::Key_0)) {
if (e->key() == Qt::Key_Plus || e->key() == Qt::Key_Equal || e->key() == Qt::Key_Asterisk || e->key() == ']') {
@ -2695,7 +2791,9 @@ void MediaView::mouseReleaseEvent(QMouseEvent *e) {
_pressed = false;
}
_down = OverNone;
activateControls();
if (!isHidden()) {
activateControls();
}
}
void MediaView::contextMenuEvent(QContextMenuEvent *e) {
@ -2804,10 +2902,13 @@ bool MediaView::eventFilter(QObject *obj, QEvent *e) {
auto type = e->type();
if ((type == QEvent::MouseMove || type == QEvent::MouseButtonPress || type == QEvent::MouseButtonRelease) && obj->isWidgetType()) {
if (isAncestorOf(static_cast<QWidget*>(obj))) {
auto mouseEvent = static_cast<QMouseEvent*>(e);
auto mousePosition = mapFromGlobal(mouseEvent->globalPos());
bool activate = (mousePosition != _lastMouseMovePos);
_lastMouseMovePos = mousePosition;
const auto mouseEvent = static_cast<QMouseEvent*>(e);
const auto mousePosition = mapFromGlobal(mouseEvent->globalPos());
const auto delta = (mousePosition - _lastMouseMovePos);
auto activate = delta.manhattanLength() >= st::mediaviewDeltaFromLastAction;
if (activate) {
_lastMouseMovePos = mousePosition;
}
if (type == QEvent::MouseButtonPress) {
_mousePressed = true;
activate = true;
@ -2815,7 +2916,12 @@ bool MediaView::eventFilter(QObject *obj, QEvent *e) {
_mousePressed = false;
activate = true;
}
if (activate) activateControls();
if (activate) {
if (_controlsState == ControlsHiding || _controlsState == ControlsHidden) {
int a = 0;
}
activateControls();
}
}
}
return TWidget::eventFilter(obj, e);
@ -2832,6 +2938,8 @@ void MediaView::setVisible(bool visible) {
_controlsHideTimer.stop();
_controlsState = ControlsShown;
a_cOpacity = anim::value(1, 1);
_groupThumbs = nullptr;
_groupThumbsRect = QRect();
}
TWidget::setVisible(visible);
if (visible) {
@ -2936,20 +3044,3 @@ float64 MediaView::overLevel(OverState control) const {
auto i = _animOpacities.constFind(control);
return (i == _animOpacities.cend()) ? (_over == control ? 1 : 0) : i->current();
}
bool MediaView::groupThumbsDisplayed() const {
return _groupThumbs != nullptr;
}
QRect MediaView::groupThumbsRect() const {
Expects(_groupThumbs != nullptr);
auto result = _groupThumbs->paintedRect();
result.moveTopLeft(result.topLeft()
+ QPoint(_groupThumbsLeft, _groupThumbsTop));
return result;
}
QRect MediaView::groupThumbsFullRect() const {
return QRect(0, width(), _groupThumbsTop, height() - _groupThumbsTop);
}

View File

@ -189,6 +189,7 @@ private:
void refreshCaption(HistoryItem *item);
void refreshMediaViewer();
void refreshNavVisibility();
void refreshGroupThumbs();
void dropdownHidden();
void updateDocSize();
@ -210,7 +211,8 @@ private:
void toggleVideoPaused();
void createClipController();
void setClipControllerGeometry();
void refreshClipControllerGeometry();
void refreshCaptionGeometry();
void initAnimation();
void createClipReader();
@ -248,9 +250,8 @@ private:
bool updateOverState(OverState newState);
float64 overLevel(OverState control) const;
bool groupThumbsDisplayed() const;
QRect groupThumbsRect() const;
QRect groupThumbsFullRect() const;
void checkGroupThumbsAnimation();
QBrush _transparentBrush;
@ -279,6 +280,8 @@ private:
int _fullScreenZoomCache = 0;
std::unique_ptr<Media::View::GroupThumbs> _groupThumbs;
QRect _groupThumbsRect;
int _groupThumbsAvailableWidth = 0;
int _groupThumbsLeft = 0;
int _groupThumbsTop = 0;
Text _caption;