diff --git a/Telegram/Resources/icons/info_media_delete.png b/Telegram/Resources/icons/info_media_delete.png index 6535df215a..0898e37910 100644 Binary files a/Telegram/Resources/icons/info_media_delete.png and b/Telegram/Resources/icons/info_media_delete.png differ diff --git a/Telegram/Resources/icons/info_media_delete@2x.png b/Telegram/Resources/icons/info_media_delete@2x.png index 29950a1f76..3b3b3fc335 100644 Binary files a/Telegram/Resources/icons/info_media_delete@2x.png and b/Telegram/Resources/icons/info_media_delete@2x.png differ diff --git a/Telegram/Resources/icons/info_media_forward.png b/Telegram/Resources/icons/info_media_forward.png index 9ab287d184..c1cff1fc5c 100644 Binary files a/Telegram/Resources/icons/info_media_forward.png and b/Telegram/Resources/icons/info_media_forward.png differ diff --git a/Telegram/Resources/icons/info_media_forward@2x.png b/Telegram/Resources/icons/info_media_forward@2x.png index 36c7d299f9..8f879ebd04 100644 Binary files a/Telegram/Resources/icons/info_media_forward@2x.png and b/Telegram/Resources/icons/info_media_forward@2x.png differ diff --git a/Telegram/SourceFiles/info/info_top_bar.cpp b/Telegram/SourceFiles/info/info_top_bar.cpp index 38f6baf79f..b5c6217d93 100644 --- a/Telegram/SourceFiles/info/info_top_bar.cpp +++ b/Telegram/SourceFiles/info/info_top_bar.cpp @@ -21,12 +21,16 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "info/info_top_bar.h" #include +#include #include "styles/style_info.h" #include "lang/lang_keys.h" #include "info/info_wrap_widget.h" #include "info/info_controller.h" #include "info/profile/info_profile_values.h" #include "storage/storage_shared_media.h" +#include "boxes/confirm_box.h" +#include "boxes/peer_list_controllers.h" +#include "mainwidget.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" #include "ui/widgets/input_fields.h" @@ -37,30 +41,48 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org namespace Info { -TopBar::TopBar(QWidget *parent, const style::InfoTopBar &st) +TopBar::TopBar( + QWidget *parent, + const style::InfoTopBar &st, + SelectedItems &&selectedItems) : RpWidget(parent) -, _st(st) { +, _st(st) +, _selectedItems(Section::MediaType::kCount) { setAttribute(Qt::WA_OpaquePaintEvent); + setSelectedItems(std::move(selectedItems)); + finishSelectionAnimations(); } void TopBar::setTitle(rpl::producer &&title) { - _title.create(this, std::move(title), _st.title); + if (_title) { + delete _title; + } + _title = Ui::CreateChild>( + this, + object_ptr(this, std::move(title), _st.title)); + _title->toggle(!selectionMode(), anim::type::instant); + _defaultControls.push_back(_title.data()); + if (_back) { _title->setAttribute(Qt::WA_TransparentForMouseEvents); } updateControlsGeometry(width()); } -void TopBar::enableBackButton(bool enable) { - if (enable) { - _back.create(this, _st.back); - _back->clicks() - | rpl::start_to_stream(_backClicks, _back->lifetime()); - } else { - _back.destroy(); +void TopBar::enableBackButton() { + if (_back) { + return; } + _back = Ui::CreateChild>( + this, + object_ptr(this, _st.back)); + _back->toggle(!selectionMode(), anim::type::instant); + _back->entity()->clicks() + | rpl::start_to_stream(_backClicks, _back->lifetime()); + _defaultControls.push_back(_back.data()); + if (_title) { - _title->setAttribute(Qt::WA_TransparentForMouseEvents, enable); + _title->setAttribute(Qt::WA_TransparentForMouseEvents); } updateControlsGeometry(width()); } @@ -104,26 +126,30 @@ void TopBar::createSearchView( field->setParent(wrap); auto search = addButton( - base::make_unique_q>( + base::make_unique_q>>( this, - object_ptr(this, _st.search))); - auto cancel = Ui::CreateChild( + object_ptr>( + this, + object_ptr(this, _st.search)))); + _defaultControls.push_back(search); + auto cancel = Ui::CreateChild>( wrap, - _st.searchRow.fieldCancel); + object_ptr(wrap, _st.searchRow.fieldCancel)); + _defaultControls.push_back(cancel); auto toggleSearchMode = [=](bool enabled, anim::type animated) { if (!enabled) { setFocus(); } if (_title) { - _title->setVisible(!enabled); + _title->entity()->setVisible(!enabled); } field->setVisible(enabled); - cancel->toggleAnimated(enabled); + cancel->entity()->toggleAnimated(enabled); if (animated == anim::type::instant) { - cancel->finishAnimations(); + cancel->entity()->finishAnimations(); } - search->toggle(!enabled, animated); + search->wrapped()->toggle(!enabled, animated); if (enabled) { field->setFocus(); } @@ -137,7 +163,7 @@ void TopBar::createSearchView( } }; - cancel->addClickHandler(cancelSearch); + cancel->entity()->addClickHandler(cancelSearch); field->connect(field, &Ui::InputField::cancelled, cancelSearch); wrap->widthValue() @@ -187,7 +213,7 @@ void TopBar::createSearchView( } toggleSearchMode(false, anim::type::instant); wrap->setVisible(visible); - search->toggle(visible, anim::type::instant); + search->wrapped()->toggle(visible, anim::type::instant); }, wrap->lifetime()); } @@ -202,7 +228,23 @@ int TopBar::resizeGetHeight(int newWidth) { return _st.height; } +void TopBar::finishSelectionAnimations() { + ranges::for_each(ranges::view::concat( + _defaultControls, + _selectionControls + ), [](auto &&control) { + if (auto pointer = control.data()) { + pointer->finishAnimating(); + } + }); +} + void TopBar::updateControlsGeometry(int newWidth) { + updateDefaultControlsGeometry(newWidth); + updateSelectionControlsGeometry(newWidth); +} + +void TopBar::updateDefaultControlsGeometry(int newWidth) { auto right = 0; for (auto &button : _buttons) { if (!button) continue; @@ -225,6 +267,32 @@ void TopBar::updateControlsGeometry(int newWidth) { } } +void TopBar::updateSelectionControlsGeometry(int newWidth) { + if (!_selectionText) { + return; + } + + auto right = _st.mediaActionsSkip; + if (_canDelete) { + _delete->moveToRight(right, 0, newWidth); + right += _delete->width(); + } + _forward->moveToRight(right, 0, newWidth); + right += _forward->width(); + + auto left = 0; + _cancelSelection->moveToLeft(left, 0); + left += _cancelSelection->width(); + + const auto top = 0; + const auto availableWidth = newWidth - left - right; + _selectionText->resizeToWidth(availableWidth); + _selectionText->moveToLeft( + left, + top, + newWidth); +} + void TopBar::paintEvent(QPaintEvent *e) { Painter p(this); @@ -251,6 +319,182 @@ void TopBar::startHighlightAnimation() { _st.highlightDuration); } +void TopBar::setSelectedItems(SelectedItems &&items) { + _selectedItems = std::move(items); + if (selectionMode()) { + if (_selectionText) { + updateSelectionState(); + } else { + createSelectionControls(); + } + } + toggleSelectionControls(); +} + +SelectedItems TopBar::takeSelectedItems() { + _canDelete = false; + return std::move(_selectedItems); +} + +rpl::producer<> TopBar::cancelSelectionRequests() const { + return _cancelSelectionClicks.events(); +} + +void TopBar::updateSelectionState() { + Expects(_selectionText && _delete); + + _canDelete = computeCanDelete(); + _selectionText->entity()->setValue(generateSelectedText()); + _delete->entity()->setVisible(_canDelete); + + updateSelectionControlsGeometry(width()); +} + +void TopBar::createSelectionControls() { + auto wrap = [&](auto created) { + _selectionControls.push_back(created); + created->toggle(false, anim::type::instant); + return created; + }; + _canDelete = computeCanDelete(); + _cancelSelection = wrap(Ui::CreateChild>( + this, + object_ptr(this, _st.mediaCancel))); + _cancelSelection->entity()->clicks() + | rpl::start_to_stream( + _cancelSelectionClicks, + _cancelSelection->lifetime()); + _selectionText = wrap(Ui::CreateChild>( + this, + object_ptr( + this, + _st.title, + _st.titlePosition.y(), + generateSelectedText()))); + _selectionText->entity()->resize(0, _st.height); + _forward = wrap(Ui::CreateChild>( + this, + object_ptr(this, _st.mediaForward))); + _forward->entity()->addClickHandler([this] { performForward(); }); + _delete = wrap(Ui::CreateChild>( + this, + object_ptr(this, _st.mediaDelete))); + _delete->entity()->addClickHandler([this] { performDelete(); }); + _delete->entity()->setVisible(_canDelete); + + updateControlsGeometry(width()); +} + +bool TopBar::computeCanDelete() const { + return ranges::find_if( + _selectedItems.list, + [](const SelectedItem &item) { return !item.canDelete; } + ) == _selectedItems.list.end(); +} + +void TopBar::toggleSelectionControls() { + auto toggle = [](bool shown) { + return [=](auto &&control) { + if (auto pointer = control.data()) { + pointer->toggle(shown, anim::type::normal); + } + }; + }; + auto shown = selectionMode(); + ranges::for_each(_defaultControls, toggle(!shown)); + ranges::for_each(_selectionControls, toggle(shown)); + + if (!shown) { + clearSelectionControls(); + } +} + +Ui::StringWithNumbers TopBar::generateSelectedText() const { + using Data = Ui::StringWithNumbers; + using Type = Storage::SharedMediaType; + auto phrase = [&] { + switch (_selectedItems.type) { + case Type::Photo: return lng_media_selected_photo__generic; + case Type::Video: return lng_media_selected_video__generic; + case Type::File: return lng_media_selected_file__generic; + case Type::MusicFile: return lng_media_selected_song__generic; + case Type::Link: return lng_media_selected_link__generic; + case Type::VoiceFile: return lng_media_selected_audio__generic; +// case Type::RoundFile: return lng_media_selected_round__generic; + } + Unexpected("Type in TopBarOverride::generateText()"); + }(); + return phrase(lt_count, _selectedItems.list.size()); +} + +bool TopBar::selectionMode() const { + return !_selectedItems.list.empty(); +} + +void TopBar::clearSelectionControls() { + for (auto &&control : _selectionControls) { + if (auto pointer = control.data()) { + pointer->shownValue() + | rpl::filter([](bool shown) { return !shown; }) + | rpl::start_with_next([control] { + if (auto pointer = control.data()) { + InvokeQueued(pointer, [pointer] { delete pointer; }); + } + }, pointer->lifetime()); + } + } + + auto isStale = [](auto &&control) { return !control; }; + _defaultControls |= ranges::action::remove_if(isStale); + _selectionControls |= ranges::action::remove_if(isStale); + + _cancelSelection = nullptr; + _selectionText = nullptr; + _forward = nullptr; + _delete = nullptr; +} + +SelectedItemSet TopBar::collectItems() const { + auto result = SelectedItemSet(); + for (auto value : _selectedItems.list) { + if (auto item = App::histItemById(value.msgId)) { + result.insert(result.size(), item); + } + } + return result; +} + +void TopBar::performForward() { + auto items = collectItems(); + if (items.empty()) { + _cancelSelectionClicks.fire({}); + return; + } + auto callback = [items = std::move(items), that = weak(this)]( + not_null peer) { + App::main()->setForwardDraft(peer->id, items); + if (that) { + that->_cancelSelectionClicks.fire({}); + } + }; + Ui::show(Box( + std::make_unique(std::move(callback)), + [](not_null box) { + box->addButton(langFactory(lng_cancel), [box] { + box->closeBox(); + }); + })); +} + +void TopBar::performDelete() { + auto items = collectItems(); + if (items.empty()) { + _cancelSelectionClicks.fire({}); + } else { + Ui::show(Box(items)); + } +} + rpl::producer TitleValue( const Section §ion, not_null peer) { diff --git a/Telegram/SourceFiles/info/info_top_bar.h b/Telegram/SourceFiles/info/info_top_bar.h index 24788d6ab3..9777ce0aa8 100644 --- a/Telegram/SourceFiles/info/info_top_bar.h +++ b/Telegram/SourceFiles/info/info_top_bar.h @@ -21,6 +21,9 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #pragma once #include "ui/rp_widget.h" +#include "ui/wrap/fade_wrap.h" +#include "ui/effects/numbers_animation.h" +#include "info/info_wrap_widget.h" namespace style { struct InfoTopBar; @@ -31,6 +34,7 @@ class IconButton; class FlatLabel; class InputField; class SearchFieldController; +class LabelWithNumbers; } // namespace Ui namespace Info { @@ -43,14 +47,17 @@ rpl::producer TitleValue( class TopBar : public Ui::RpWidget { public: - TopBar(QWidget *parent, const style::InfoTopBar &st); + TopBar( + QWidget *parent, + const style::InfoTopBar &st, + SelectedItems &&items); auto backRequest() const { return _backClicks.events(); } void setTitle(rpl::producer &&title); - void enableBackButton(bool enable); + void enableBackButton(); void highlight(); template @@ -64,16 +71,37 @@ public: not_null controller, rpl::producer &&shown); + void setSelectedItems(SelectedItems &&items); + SelectedItems takeSelectedItems(); + + rpl::producer<> cancelSelectionRequests() const; + void finishSelectionAnimations(); + protected: int resizeGetHeight(int newWidth) override; void paintEvent(QPaintEvent *e) override; private: void updateControlsGeometry(int newWidth); + void updateDefaultControlsGeometry(int newWidth); + void updateSelectionControlsGeometry(int newWidth); void pushButton(base::unique_qptr button); void removeButton(not_null button); void startHighlightAnimation(); + bool selectionMode() const; + Ui::StringWithNumbers generateSelectedText() const; + [[nodiscard]] bool computeCanDelete() const; + [[nodiscard]] SelectedItemSet collectSelectedItems() const; + void updateSelectionState(); + void createSelectionControls(); + void toggleSelectionControls(); + void clearSelectionControls(); + + SelectedItemSet collectItems() const; + void performForward(); + void performDelete(); + void setSearchField( base::unique_qptr field, rpl::producer &&shown); @@ -84,14 +112,26 @@ private: const style::InfoTopBar &_st; Animation _a_highlight; bool _highlight = false; - object_ptr _back = { nullptr }; + QPointer> _back; std::vector> _buttons; - object_ptr _title = { nullptr }; + QPointer> _title; base::unique_qptr _searchView; rpl::event_stream<> _backClicks; + SelectedItems _selectedItems; + bool _canDelete = false; + QPointer> _cancelSelection; + QPointer> _selectionText; + QPointer> _forward; + QPointer> _delete; + rpl::event_stream<> _cancelSelectionClicks; + + using FadingControl = QPointer>; + std::vector _defaultControls; + std::vector _selectionControls; + }; } // namespace Info diff --git a/Telegram/SourceFiles/info/info_wrap_widget.cpp b/Telegram/SourceFiles/info/info_wrap_widget.cpp index 6daa6e825e..45e00db5cc 100644 --- a/Telegram/SourceFiles/info/info_wrap_widget.cpp +++ b/Telegram/SourceFiles/info/info_wrap_widget.cpp @@ -30,7 +30,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "info/info_controller.h" #include "info/info_memento.h" #include "info/info_top_bar.h" -#include "info/info_top_bar_override.h" #include "ui/widgets/discrete_sliders.h" #include "ui/widgets/buttons.h" #include "ui/widgets/shadow.h" @@ -84,7 +83,7 @@ WrapWidget::WrapWidget( selectedListValue() | rpl::start_with_next([this](SelectedItems &&items) { InvokeQueued(this, [this, items = std::move(items)]() mutable { - refreshTopBarOverride(std::move(items)); + if (_topBar) _topBar->setSelectedItems(std::move(items)); }); }, lifetime()); restoreHistoryStack(memento->takeStack()); @@ -283,18 +282,24 @@ void WrapWidget::setupTop() { // createTopBar(); //} createTopBar(); - refreshTopBarOverride(); } void WrapWidget::createTopBar() { auto wrapValue = wrap(); - _topBar.create(this, TopBarStyle(wrapValue)); + auto selectedItems = _topBar + ? _topBar->takeSelectedItems() + : SelectedItems(Section::MediaType::kCount); + _topBar.create(this, TopBarStyle(wrapValue), std::move(selectedItems)); + _topBar->cancelSelectionRequests() + | rpl::start_with_next([this](auto) { + _content->cancelSelection(); + }, _topBar->lifetime()); _topBar->setTitle(TitleValue( _controller->section(), _controller->peer())); if (wrapValue == Wrap::Narrow || hasStackHistory()) { - _topBar->enableBackButton(true); + _topBar->enableBackButton(); _topBar->backRequest() | rpl::start_with_next([this] { showBackFromStack(); @@ -418,81 +423,6 @@ void WrapWidget::showProfileMenu() { _topBarMenu->showAnimated(Ui::PanelAnimation::Origin::TopRight); } -void WrapWidget::refreshTopBarOverride(SelectedItems &&items) { - auto empty = items.list.empty(); - if (!empty) { - if (_topBarOverride) { - _topBarOverride->setItems(std::move(items)); - } else { - createTopBarOverride(std::move(items)); - } - } - toggleTopBarOverride(!empty); -} - -void WrapWidget::refreshTopBarOverride() { - if (_topBarOverride) { - auto items = _topBarOverride->takeItems(); - createTopBarOverride(std::move(items)); - topBarOverrideStep(); - } -} - -void WrapWidget::toggleTopBarOverride(bool shown) { - if (_topBarOverrideShown == shown) { - return; - } - _topBarOverrideShown = shown; - _topBar->show(); - _topBarOverrideAnimation.start( - [this] { topBarOverrideStep(); }, - _topBarOverrideShown ? 0. : 1., - _topBarOverrideShown ? 1. : 0., - st::slideWrapDuration, - anim::easeOutCirc); -} - -void WrapWidget::topBarOverrideStep() { - auto shown = _topBarOverrideAnimation.current( - _topBarOverrideShown ? 1. : 0.); - auto topBarTop = anim::interpolate(0, _topBar->height(), shown); - auto overrideTop = anim::interpolate(-_topBar->height(), 0, shown); - _topBar->moveToLeft(0, topBarTop); - if (_topBarOverride) { - _topBarOverride->moveToLeft(0, overrideTop); - } - if (!_topBarOverrideAnimation.animating()) { - if (_topBarOverrideShown) { - _topBar->hide(); - } else { - _topBarOverride = nullptr; - } - } -} - -void WrapWidget::createTopBarOverride(SelectedItems &&items) { - _topBarOverride.create( - this, - TopBarStyle(wrap()), - std::move(items)); - - // This was done for tabs support. - // - //if (_topTabs) { - // _topTabs->hide(); - //} - - if (_topBar) { - _topBar->hide(); - } - _topBarOverride->cancelRequests() - | rpl::start_with_next([this](auto) { - _content->cancelSelection(); - }, _topBarOverride->lifetime()); - _topBarOverride->resizeToWidth(width()); - _topBarOverride->show(); -} - bool WrapWidget::requireTopBarSearch() const { if (!_controller->searchFieldController()) { return false; @@ -832,10 +762,6 @@ void WrapWidget::showNewContent( showNewContent(memento); } if (animationParams) { - refreshTopBarOverride(SelectedItems(Section::MediaType::kCount)); - _topBarOverrideAnimation.finish(); - topBarOverrideStep(); - showAnimated( saveToStack ? SlideDirection::FromRight @@ -876,9 +802,6 @@ void WrapWidget::resizeEvent(QResizeEvent *e) { if (_topBar) { _topBar->resizeToWidth(width()); } - if (_topBarOverride) { - _topBarOverride->resizeToWidth(width()); - } updateContentGeometry(); } diff --git a/Telegram/SourceFiles/info/info_wrap_widget.h b/Telegram/SourceFiles/info/info_wrap_widget.h index ec1f1f86d2..ed7f44c6d1 100644 --- a/Telegram/SourceFiles/info/info_wrap_widget.h +++ b/Telegram/SourceFiles/info/info_wrap_widget.h @@ -52,7 +52,6 @@ class MoveMemento; class ContentMemento; class ContentWidget; class TopBar; -class TopBarOverride; enum class Wrap { Layer, @@ -190,11 +189,6 @@ private: //void convertProfileFromStackToTab(); rpl::producer selectedListValue() const; - void refreshTopBarOverride(); - void refreshTopBarOverride(SelectedItems &&items); - void createTopBarOverride(SelectedItems &&items); - void toggleTopBarOverride(bool shown); - void topBarOverrideStep(); bool requireTopBarSearch() const; void addProfileMenuButton(); @@ -209,7 +203,6 @@ private: //object_ptr _topTabs = { nullptr }; object_ptr _topBar = { nullptr }; object_ptr _topBarSurrogate = { nullptr }; - object_ptr _topBarOverride = { nullptr }; Animation _topBarOverrideAnimation; bool _topBarOverrideShown = false; object_ptr _topShadow; diff --git a/Telegram/SourceFiles/info/media/info_media_list_widget.cpp b/Telegram/SourceFiles/info/media/info_media_list_widget.cpp index c41cb095d0..554ecaa2ac 100644 --- a/Telegram/SourceFiles/info/media/info_media_list_widget.cpp +++ b/Telegram/SourceFiles/info/media/info_media_list_widget.cpp @@ -794,6 +794,10 @@ void ListWidget::refreshViewer() { _idsLimit) | rpl::start_with_next([=]( SparseIdsMergedSlice &&slice) { + if (!slice.fullCount()) { + // Don't display anything while full count is unknown. + return; + } _slice = std::move(slice); if (auto nearest = _slice.nearest(idForViewer)) { _universalAroundId = GetUniversalId(*nearest); @@ -906,8 +910,10 @@ void ListWidget::refreshRows() { _sections.push_back(std::move(section)); } - if (_layouts.size() > kMediaCountForSearch) { - _controller->setSearchEnabledByContent(true); + if (auto count = _slice.fullCount()) { + if (*count > kMediaCountForSearch) { + _controller->setSearchEnabledByContent(true); + } } clearStaleLayouts();