From ecbc0ae57ea2af0ebe8ad2784928979a29cdff45 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 4 Oct 2017 18:15:08 +0100 Subject: [PATCH] Show info media overview using Overview::Layout. --- .../history/history_shared_media.cpp | 6 +- Telegram/SourceFiles/info/info.style | 12 +- .../SourceFiles/info/info_wrap_widget.cpp | 8 +- .../info/media/info_media_inner_widget.cpp | 21 +- .../info/media/info_media_inner_widget.h | 1 + .../info/media/info_media_list_widget.cpp | 451 +++++++++++++++++- .../info/media/info_media_list_widget.h | 47 ++ .../info/profile/info_profile_values.cpp | 8 +- .../SourceFiles/overview/overview_layout.h | 6 +- 9 files changed, 546 insertions(+), 14 deletions(-) diff --git a/Telegram/SourceFiles/history/history_shared_media.cpp b/Telegram/SourceFiles/history/history_shared_media.cpp index dfbb301c4..138107030 100644 --- a/Telegram/SourceFiles/history/history_shared_media.cpp +++ b/Telegram/SourceFiles/history/history_shared_media.cpp @@ -510,9 +510,9 @@ SharedMediaMergedSlice::SharedMediaMergedSlice( Key key, SharedMediaSlice part, base::optional migrated) - : _key(key) - , _part(std::move(part)) - , _migrated(std::move(migrated)) { +: _key(key) +, _part(std::move(part)) +, _migrated(std::move(migrated)) { } base::optional SharedMediaMergedSlice::fullCount() const { diff --git a/Telegram/SourceFiles/info/info.style b/Telegram/SourceFiles/info/info.style index 2dc5a4062..009464667 100644 --- a/Telegram/SourceFiles/info/info.style +++ b/Telegram/SourceFiles/info/info.style @@ -32,6 +32,8 @@ InfoToggle { rippleAreaPadding: pixels; } +infoMediaHeaderFg: windowFg; + infoToggleCheckbox: Checkbox(defaultCheckbox) { margin: margins(0px, 0px, 0px, 0px); rippleBgActive: windowBgOver; @@ -113,7 +115,7 @@ infoLayerTopBar: InfoTopBar { bg: boxBg; } -infoMinimalWidth: 320px; +infoMinimalWidth: 324px; infoDesiredWidth: 360px; infoMinimalLayerMargin: 48px; @@ -342,3 +344,11 @@ infoMembersCancelSearch: CrossButton { } } infoMembersSearchTop: 15px; + +infoMediaHeaderStyle: TextStyle(semiboldTextStyle) { +} +infoMediaHeaderHeight: 28px; +infoMediaHeaderPosition: point(14px, 6px); +infoMediaSkip: 6px; +infoMediaMargin: margins(0px, 6px, 0px, 2px); +infoMediaMinGridSize: minPhotoSize; diff --git a/Telegram/SourceFiles/info/info_wrap_widget.cpp b/Telegram/SourceFiles/info/info_wrap_widget.cpp index 1588fd283..c1406d1b5 100644 --- a/Telegram/SourceFiles/info/info_wrap_widget.cpp +++ b/Telegram/SourceFiles/info/info_wrap_widget.cpp @@ -79,6 +79,9 @@ void WrapWidget::createTabs() { sections.push_back(lang(lng_profile_info_section).toUpper()); sections.push_back(lang(lng_info_tab_media).toUpper()); _topTabs->setSections(sections); + _topTabs->setActiveSection(static_cast(_tab)); + _topTabs->finishAnimating(); + _topTabs->sectionActivated() | rpl::map([](int index) { return static_cast(index); }) | rpl::start_with_next( @@ -111,6 +114,9 @@ void WrapWidget::forceContentRepaint() { } void WrapWidget::showTab(Tab tab) { + if (_tab == tab) { + return; + } Expects(_content != nullptr); auto direction = (tab > _tab) ? SlideDirection::FromRight @@ -132,6 +138,7 @@ void WrapWidget::showTab(Tab tab) { showAnimated(direction, animationParams); _anotherTabMemento = std::move(newAnotherMemento); + _tab = tab; } void WrapWidget::setupTabbedTop(const Section §ion) { @@ -241,7 +248,6 @@ void WrapWidget::finishShowContent() { _topShadow->finishAnimating(); if (_topTabs) { _topTabs->raise(); - _topTabs->finishAnimating(); } } diff --git a/Telegram/SourceFiles/info/media/info_media_inner_widget.cpp b/Telegram/SourceFiles/info/media/info_media_inner_widget.cpp index ec84443ad..0314f8229 100644 --- a/Telegram/SourceFiles/info/media/info_media_inner_widget.cpp +++ b/Telegram/SourceFiles/info/media/info_media_inner_widget.cpp @@ -26,6 +26,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "info/profile/info_profile_button.h" #include "info/profile/info_profile_icon.h" #include "ui/widgets/discrete_sliders.h" +#include "ui/widgets/shadow.h" #include "ui/wrap/vertical_layout.h" #include "styles/style_info.h" #include "lang/lang_keys.h" @@ -82,8 +83,12 @@ void InnerWidget::setupOtherTypes(rpl::producer &&wrap) { } void InnerWidget::createOtherTypes() { + _otherTabsShadow.create(this); + _otherTabsShadow->show(); + _otherTabs = nullptr; _otherTypes.create(this); + _otherTypes->show(); createTypeButtons(); _otherTypes->add(object_ptr(_otherTypes)); @@ -157,6 +162,9 @@ void InnerWidget::createTabs() { sections.push_back(lang(lng_media_type_videos).toUpper()); sections.push_back(lang(lng_media_type_files).toUpper()); _otherTabs->setSections(sections); + _otherTabs->setActiveSection(*TypeToTabIndex(type())); + _otherTabs->finishAnimating(); + _otherTabs->sectionActivated() | rpl::map([](int index) { return TabIndexToType(index); }) | rpl::start_with_next( @@ -204,7 +212,14 @@ void InnerWidget::switchToTab(Memento &&memento) { auto type = memento.section().mediaType(); _list = setupList(controller(), peer(), type); restoreState(&memento); - _otherTabs->setActiveSection(*TypeToTabIndex(type)); + _list->show(); + _list->resizeToWidth(width()); + refreshHeight(); + if (_otherTypes) { + _otherTabsShadow->raise(); + _otherTypes->raise(); + _otherTabs->setActiveSection(*TypeToTabIndex(type)); + } } not_null InnerWidget::controller() const { @@ -239,6 +254,7 @@ int InnerWidget::resizeGetHeight(int newWidth) { if (_otherTypes) { _otherTypes->resizeToWidth(newWidth); + _otherTabsShadow->resizeToWidth(newWidth); } _list->resizeToWidth(newWidth); return recountHeight(); @@ -255,7 +271,8 @@ int InnerWidget::recountHeight() { auto top = 0; if (_otherTypes) { _otherTypes->moveToLeft(0, top); - top += _otherTypes->heightNoMargins(); + top += _otherTypes->heightNoMargins() - st::lineWidth; + _otherTabsShadow->moveToLeft(0, top); } if (_list) { _list->moveToLeft(0, top); diff --git a/Telegram/SourceFiles/info/media/info_media_inner_widget.h b/Telegram/SourceFiles/info/media/info_media_inner_widget.h index 33c3bbe77..d43652a4c 100644 --- a/Telegram/SourceFiles/info/media/info_media_inner_widget.h +++ b/Telegram/SourceFiles/info/media/info_media_inner_widget.h @@ -78,6 +78,7 @@ private: Ui::SettingsSlider *_otherTabs = nullptr; object_ptr _otherTypes = { nullptr }; + object_ptr _otherTabsShadow = { nullptr }; object_ptr _list = { nullptr }; int _visibleTop = 0; diff --git a/Telegram/SourceFiles/info/media/info_media_list_widget.cpp b/Telegram/SourceFiles/info/media/info_media_list_widget.cpp index 696f6fba1..e47d458f3 100644 --- a/Telegram/SourceFiles/info/media/info_media_list_widget.cpp +++ b/Telegram/SourceFiles/info/media/info_media_list_widget.cpp @@ -20,8 +20,271 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "info/media/info_media_list_widget.h" +#include "overview/overview_layout.h" +#include "history/history_media_types.h" +#include "window/themes/window_theme.h" +#include "lang/lang_keys.h" +#include "styles/style_overview.h" +#include "styles/style_info.h" + +namespace Layout = Overview::Layout; + namespace Info { namespace Media { +namespace { + +constexpr auto kIdsLimit = 256; + +using ItemBase = Layout::ItemBase; + +} // namespace + +ListWidget::CachedItem::CachedItem(std::unique_ptr item) +: item(std::move(item)) { +} + +ListWidget::CachedItem::~CachedItem() = default; + +class ListWidget::Section { +public: + Section(Type type) : _type(type) { + } + + bool addItem(not_null item); + bool empty() const { + return _items.empty(); + } + + void resizeToWidth(int newWidth); + int height() const { + return _height; + } + + void paint( + Painter &p, + QRect clip, + int outerWidth, + TimeMs ms) const; + +private: + int headerHeight() const; + void appendItem(not_null item); + void setHeader(not_null item); + bool belongsHere(not_null item) const; + int countRowHeight(not_null item) const; + + Type _type = Type::Photo; + Text _header; + std::vector> _items; + int _itemsLeft = 0; + int _itemsTop = 0; + int _itemWidth = 0; + int _itemsInRow = 1; + int _rowsCount = 0; + int _height = 0; + +}; + +bool ListWidget::Section::addItem(not_null item) { + if (_items.empty() || belongsHere(item)) { + if (_items.empty()) setHeader(item); + appendItem(item); + return true; + } + return false; +} + +void ListWidget::Section::setHeader(not_null item) { + auto text = [&] { + auto date = item->getItem()->date.date(); + switch (_type) { + case Type::Photo: + case Type::Video: + case Type::RoundFile: + case Type::VoiceFile: + case Type::File: + return langMonthFull(date); + + case Type::Link: + return langDayOfMonthFull(date); + + case Type::MusicFile: + return QString(); + } + Unexpected("Type in ListWidget::Section::setHeader()"); + }(); + _header.setText(st::infoMediaHeaderStyle, text); +} + +bool ListWidget::Section::belongsHere( + not_null item) const { + Expects(!_items.empty()); + auto date = item->getItem()->date.date(); + auto myDate = _items.back()->getItem()->date.date(); + + switch (_type) { + case Type::Photo: + case Type::Video: + case Type::RoundFile: + case Type::VoiceFile: + case Type::File: + return date.year() == myDate.year() + && date.month() == myDate.month(); + + case Type::Link: + return date.year() == myDate.year() + && date.month() == myDate.month() + && date.day() == myDate.day(); + + case Type::MusicFile: + return true; + } + Unexpected("Type in ListWidget::Section::belongsHere()"); +} + +int ListWidget::Section::countRowHeight( + not_null item) const { + switch (_type) { + case Type::Photo: + case Type::Video: + case Type::RoundFile: + return _itemWidth + st::infoMediaSkip; + + case Type::VoiceFile: + case Type::File: + case Type::Link: + case Type::MusicFile: + return item->height(); + } + Unexpected("Type in ListWidget::Section::countRowHeight()"); +} + +void ListWidget::Section::appendItem(not_null item) { + _items.push_back(item); +} + +void ListWidget::Section::paint( + Painter &p, + QRect clip, + int outerWidth, + TimeMs ms) const { + auto baseIndex = 0; + auto top = _itemsTop; + auto header = headerHeight(); + if (header) { + p.setPen(st::infoMediaHeaderFg); + _header.drawLeftElided( + p, + st::infoMediaHeaderPosition.x(), + st::infoMediaHeaderPosition.y(), + outerWidth - 2 * st::infoMediaHeaderPosition.x(), + outerWidth); + top += header; + } + auto fromitem = floorclamp( + clip.x() - _itemsLeft, + _itemWidth, + 0, + _itemsInRow); + auto tillitem = ceilclamp( + clip.x() + clip.width() - _itemsLeft, + _itemWidth, + 0, + _itemsInRow); + Layout::PaintContext context(ms, false); + context.isAfterDate = (header > 0); + + // #TODO ranges, binary search for visible slice. + for (auto row = 0; row != _rowsCount; ++row) { + auto rowHeight = countRowHeight(_items[baseIndex]); + auto increment = gsl::finally([&] { + top += rowHeight; + baseIndex += _itemsInRow; + context.isAfterDate = false; + }); + + if (top >= clip.y() + clip.height()) { + break; + } else if (top + rowHeight <= clip.y()) { + continue; + } + for (auto col = fromitem; col != tillitem; ++col) { + auto index = baseIndex + col; + if (index >= int(_items.size())) { + break; + } + auto item = _items[index]; + auto left = _itemsLeft + + col * (_itemWidth + st::infoMediaSkip); + p.translate(left, top); + item->paint( + p, + clip.translated(-left, -top), + TextSelection(), + &context); + p.translate(-left, -top); + } + } +} + +int ListWidget::Section::headerHeight() const { + return _header.isEmpty() ? 0 : st::infoMediaHeaderHeight; +} + +void ListWidget::Section::resizeToWidth(int newWidth) { + auto minWidth = st::infoMediaMinGridSize + st::infoMediaSkip * 2; + if (newWidth < minWidth) { + return; + } + + _height = headerHeight(); + switch (_type) { + case Type::Photo: + case Type::Video: + case Type::RoundFile: { + _itemsLeft = st::infoMediaSkip; + _itemsTop = st::infoMediaSkip; + _itemsInRow = (newWidth - _itemsLeft) + / (st::infoMediaMinGridSize + st::infoMediaSkip); + _itemWidth = ((newWidth - _itemsLeft) / _itemsInRow) + - st::infoMediaSkip; + auto itemHeight = _itemWidth + st::infoMediaSkip; + _rowsCount = (int(_items.size()) + _itemsInRow - 1) + / _itemsInRow; + _height += _itemsTop + _rowsCount * itemHeight; + } break; + + case Type::VoiceFile: + case Type::File: + case Type::MusicFile: { + _itemsLeft = 0; + _itemsTop = 0; + _itemsInRow = 1; + _itemWidth = newWidth; + auto itemHeight = _items.empty() ? 0 : _items.front()->height(); + _rowsCount = _items.size(); + _height += _rowsCount * itemHeight; + } break; + + case Type::Link: + _itemsLeft = 0; + _itemsTop = 0; + _itemsInRow = 1; + _itemWidth = newWidth; + auto top = 0; + for (auto item : _items) { + top += item->resizeGetHeight(_itemWidth); + } + _height += top; + break; + } + + if (_type != Type::Link) { + for (auto item : _items) { + item->resizeGetHeight(_itemWidth); + } + } +} ListWidget::ListWidget( QWidget *parent, @@ -31,8 +294,194 @@ ListWidget::ListWidget( : RpWidget(parent) , _controller(controller) , _peer(peer) -, _type(type) { +, _type(type) +, _slice(sliceKey()) { + refreshViewer(); + ObservableViewer(*Window::Theme::Background()) + | rpl::start_with_next([this](const auto &update) { + if (update.paletteChanged()) { + invalidatePaletteCache(); + } + }, lifetime()); } +void ListWidget::invalidatePaletteCache() { + for (auto &layout : _layouts) { + layout.second.item->invalidateCache(); + } +} + +SharedMediaMergedSlice::Key ListWidget::sliceKey() const { + auto universalId = _universalAroundId; + using Key = SharedMediaMergedSlice::Key; + if (auto migrateTo = _peer->migrateTo()) { + return Key(migrateTo->id, _peer->id, _type, universalId); + } else if (auto migrateFrom = _peer->migrateFrom()) { + return Key(_peer->id, migrateFrom->id, _type, universalId); + } + return Key(_peer->id, 0, _type, universalId); +} + +void ListWidget::refreshViewer() { + SharedMediaMergedViewer( + sliceKey(), + countIdsLimit(), + countIdsLimit()) + | rpl::start_with_next([this](SharedMediaMergedSlice &&slice) { + _slice = std::move(slice); + refreshRows(); + }, _viewerLifetime); +} + +int ListWidget::countIdsLimit() const { + return kIdsLimit; +} + +ItemBase *ListWidget::getLayout(const FullMsgId &itemId) { + auto it = _layouts.find(itemId); + if (it == _layouts.end()) { + if (auto layout = createLayout(itemId, _type)) { + layout->initDimensions(); + it = _layouts.emplace(itemId, std::move(layout)).first; + } else { + return nullptr; + } + } + it->second.stale = false; + return it->second.item.get(); +} + +std::unique_ptr ListWidget::createLayout( + const FullMsgId &itemId, + Type type) { + auto item = App::histItemById(itemId); + if (!item) { + return nullptr; + } + auto getPhoto = [&]() -> PhotoData* { + if (auto media = item->getMedia()) { + if (media->type() == MediaTypePhoto) { + return static_cast(media)->photo(); + } + } + return nullptr; + }; + auto getFile = [&]() -> DocumentData* { + if (auto media = item->getMedia()) { + return media->getDocument(); + } + return nullptr; + }; + switch (type) { + case Type::Photo: + if (auto photo = getPhoto()) { + return std::make_unique(photo, item); + } + return nullptr; + case Type::Video: + if (auto file = getFile()) { + return std::make_unique(file, item); + } + return nullptr; + case Type::File: + if (auto file = getFile()) { + return std::make_unique(file, item, st::overviewFileLayout); + } + return nullptr; + case Type::MusicFile: + if (auto file = getFile()) { + return std::make_unique(file, item, st::overviewFileLayout); + } + return nullptr; + case Type::VoiceFile: + if (auto file = getFile()) { + return std::make_unique(file, item, st::overviewFileLayout); + } + return nullptr; + case Type::Link: + return std::make_unique(item->getMedia(), item); + case Type::RoundFile: + return nullptr; + } + Unexpected("Type in ListWidget::createLayout()"); +} + +void ListWidget::refreshRows() { + markLayoutsStale(); + + _sections.clear(); + auto section = Section(_type); + auto count = _slice.size(); + for (auto i = count; i != 0;) { + auto itemId = _slice[--i]; + if (auto layout = getLayout(itemId)) { + if (!section.addItem(layout)) { + _sections.push_back(std::move(section)); + section = Section(_type); + section.addItem(layout); + } + } + } + if (!section.empty()) { + _sections.push_back(std::move(section)); + } + + clearStaleLayouts(); + + resizeToWidth(width()); +} + +void ListWidget::markLayoutsStale() { + for (auto &layout : _layouts) { + layout.second.stale = true; + } +} + +int ListWidget::resizeGetHeight(int newWidth) { + for (auto §ion : _sections) { + section.resizeToWidth(newWidth); + } + return recountHeight(); +} + +void ListWidget::paintEvent(QPaintEvent *e) { + Painter p(this); + + auto outerWidth = width(); + auto clip = e->rect(); + auto ms = getms(); + auto top = st::infoMediaMargin.top(); + p.translate(0, top); + clip = clip.translated(0, -top); + for (auto §ion : _sections) { + section.paint(p, clip, outerWidth, ms); + auto height = section.height(); + p.translate(0, height); + clip = clip.translated(0, -height); + } +} + +int ListWidget::recountHeight() { + auto result = 0; + for (auto §ion : _sections) { + result += section.height(); + } + return st::infoMediaMargin.top() + + result + + st::infoMediaMargin.bottom(); +} + +void ListWidget::clearStaleLayouts() { + for (auto i = _layouts.begin(); i != _layouts.end();) { + if (i->second.stale) { + i = _layouts.erase(i); + } else { + ++i; + } + } +} + +ListWidget::~ListWidget() = default; + } // namespace Media } // namespace Info diff --git a/Telegram/SourceFiles/info/media/info_media_list_widget.h b/Telegram/SourceFiles/info/media/info_media_list_widget.h index 141d2eabc..114d901a1 100644 --- a/Telegram/SourceFiles/info/media/info_media_list_widget.h +++ b/Telegram/SourceFiles/info/media/info_media_list_widget.h @@ -22,6 +22,13 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "ui/rp_widget.h" #include "info/media/info_media_widget.h" +#include "history/history_shared_media.h" + +namespace Overview { +namespace Layout { +class ItemBase; +} // namespace Layout +} // namespace Overview namespace Window { class Controller; @@ -49,11 +56,51 @@ public: return _type; } + ~ListWidget(); + +protected: + int resizeGetHeight(int newWidth) override; + void paintEvent(QPaintEvent *e) override; + private: + using ItemBase = Overview::Layout::ItemBase; + + int recountHeight(); + + void refreshViewer(); + void invalidatePaletteCache(); + void refreshRows(); + int countIdsLimit() const; + SharedMediaMergedSlice::Key sliceKey() const; + ItemBase *getLayout(const FullMsgId &itemId); + std::unique_ptr createLayout( + const FullMsgId &itemId, + Type type); + + void markLayoutsStale(); + void clearStaleLayouts(); + not_null _controller; not_null _peer; Type _type = Type::Photo; + MsgId _universalAroundId = ServerMaxMsgId - 1; + SharedMediaMergedSlice _slice; + + struct CachedItem { + CachedItem(std::unique_ptr item); + ~CachedItem(); + + std::unique_ptr item; + bool stale = false; + }; + std::map _layouts; + + class Section; + std::vector
_sections; + + rpl::lifetime _viewerLifetime; + }; } // namespace Media diff --git a/Telegram/SourceFiles/info/profile/info_profile_values.cpp b/Telegram/SourceFiles/info/profile/info_profile_values.cpp index 2c74b8365..06cc4150f 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_values.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_values.cpp @@ -191,13 +191,15 @@ rpl::producer MembersCountValue( rpl::producer SharedMediaCountValue( not_null peer, Storage::SharedMediaType type) { - auto initial = peer->migrateFrom() ? peer->migrateFrom() : peer; - auto migrated = initial->migrateTo(); + auto real = peer->migrateTo() ? peer->migrateTo() : peer; + auto migrated = real->migrateFrom() + ? real->migrateFrom() + : nullptr; auto aroundId = 0; auto limit = 0; auto updated = SharedMediaMergedViewer( SharedMediaMergedSlice::Key( - peer->id, + real->id, migrated ? migrated->id : 0, type, aroundId), diff --git a/Telegram/SourceFiles/overview/overview_layout.h b/Telegram/SourceFiles/overview/overview_layout.h index 6b4816fce..4e8c8763d 100644 --- a/Telegram/SourceFiles/overview/overview_layout.h +++ b/Telegram/SourceFiles/overview/overview_layout.h @@ -69,13 +69,13 @@ public: ItemBase(HistoryItem *parent) : _parent(parent) { } - ItemBase *toMediaItem() override { + ItemBase *toMediaItem() final override { return this; } - const ItemBase *toMediaItem() const override { + const ItemBase *toMediaItem() const final override { return this; } - HistoryItem *getItem() const override { + HistoryItem *getItem() const final override { return _parent; }