Show info media overview using Overview::Layout.

This commit is contained in:
John Preston 2017-10-04 18:15:08 +01:00
parent 7905694b31
commit ecbc0ae57e
9 changed files with 546 additions and 14 deletions

View File

@ -510,9 +510,9 @@ SharedMediaMergedSlice::SharedMediaMergedSlice(
Key key,
SharedMediaSlice part,
base::optional<SharedMediaSlice> migrated)
: _key(key)
, _part(std::move(part))
, _migrated(std::move(migrated)) {
: _key(key)
, _part(std::move(part))
, _migrated(std::move(migrated)) {
}
base::optional<int> SharedMediaMergedSlice::fullCount() const {

View File

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

View File

@ -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<int>(_tab));
_topTabs->finishAnimating();
_topTabs->sectionActivated()
| rpl::map([](int index) { return static_cast<Tab>(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 &section) {
@ -241,7 +248,6 @@ void WrapWidget::finishShowContent() {
_topShadow->finishAnimating();
if (_topTabs) {
_topTabs->raise();
_topTabs->finishAnimating();
}
}

View File

@ -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> &&wrap) {
}
void InnerWidget::createOtherTypes() {
_otherTabsShadow.create(this);
_otherTabsShadow->show();
_otherTabs = nullptr;
_otherTypes.create(this);
_otherTypes->show();
createTypeButtons();
_otherTypes->add(object_ptr<BoxContentDivider>(_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<Window::Controller*> 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);

View File

@ -78,6 +78,7 @@ private:
Ui::SettingsSlider *_otherTabs = nullptr;
object_ptr<Ui::VerticalLayout> _otherTypes = { nullptr };
object_ptr<Ui::PlainShadow> _otherTabsShadow = { nullptr };
object_ptr<ListWidget> _list = { nullptr };
int _visibleTop = 0;

View File

@ -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<ItemBase> item)
: item(std::move(item)) {
}
ListWidget::CachedItem::~CachedItem() = default;
class ListWidget::Section {
public:
Section(Type type) : _type(type) {
}
bool addItem(not_null<ItemBase*> 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<ItemBase*> item);
void setHeader(not_null<ItemBase*> item);
bool belongsHere(not_null<ItemBase*> item) const;
int countRowHeight(not_null<ItemBase*> item) const;
Type _type = Type::Photo;
Text _header;
std::vector<not_null<ItemBase*>> _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<ItemBase*> item) {
if (_items.empty() || belongsHere(item)) {
if (_items.empty()) setHeader(item);
appendItem(item);
return true;
}
return false;
}
void ListWidget::Section::setHeader(not_null<ItemBase*> 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<ItemBase*> 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<ItemBase*> 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<ItemBase*> 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<ItemBase> 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<HistoryPhoto*>(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<Layout::Photo>(photo, item);
}
return nullptr;
case Type::Video:
if (auto file = getFile()) {
return std::make_unique<Layout::Video>(file, item);
}
return nullptr;
case Type::File:
if (auto file = getFile()) {
return std::make_unique<Layout::Document>(file, item, st::overviewFileLayout);
}
return nullptr;
case Type::MusicFile:
if (auto file = getFile()) {
return std::make_unique<Layout::Document>(file, item, st::overviewFileLayout);
}
return nullptr;
case Type::VoiceFile:
if (auto file = getFile()) {
return std::make_unique<Layout::Voice>(file, item, st::overviewFileLayout);
}
return nullptr;
case Type::Link:
return std::make_unique<Layout::Link>(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 &section : _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 &section : _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 &section : _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

View File

@ -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<ItemBase> createLayout(
const FullMsgId &itemId,
Type type);
void markLayoutsStale();
void clearStaleLayouts();
not_null<Window::Controller*> _controller;
not_null<PeerData*> _peer;
Type _type = Type::Photo;
MsgId _universalAroundId = ServerMaxMsgId - 1;
SharedMediaMergedSlice _slice;
struct CachedItem {
CachedItem(std::unique_ptr<ItemBase> item);
~CachedItem();
std::unique_ptr<ItemBase> item;
bool stale = false;
};
std::map<FullMsgId, CachedItem> _layouts;
class Section;
std::vector<Section> _sections;
rpl::lifetime _viewerLifetime;
};
} // namespace Media

View File

@ -191,13 +191,15 @@ rpl::producer<int> MembersCountValue(
rpl::producer<int> SharedMediaCountValue(
not_null<PeerData*> 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),

View File

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