tdesktop/Telegram/SourceFiles/info/info_top_bar.cpp

548 lines
14 KiB
C++
Raw Normal View History

/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "info/info_top_bar.h"
#include <rpl/never.h>
2017-11-26 17:05:52 +00:00
#include <rpl/merge.h>
#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"
2017-11-26 17:05:52 +00:00
#include "boxes/confirm_box.h"
#include "boxes/peer_list_controllers.h"
#include "mainwidget.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
2017-11-03 12:03:00 +00:00
#include "ui/widgets/input_fields.h"
#include "ui/widgets/shadow.h"
2017-11-03 12:03:00 +00:00
#include "ui/wrap/fade_wrap.h"
#include "ui/wrap/padding_wrap.h"
#include "ui/search_field_controller.h"
namespace Info {
2017-11-26 17:05:52 +00:00
TopBar::TopBar(
QWidget *parent,
const style::InfoTopBar &st,
SelectedItems &&selectedItems)
: RpWidget(parent)
2017-11-26 17:05:52 +00:00
, _st(st)
, _selectedItems(Section::MediaType::kCount) {
setAttribute(Qt::WA_OpaquePaintEvent);
2017-11-26 17:05:52 +00:00
setSelectedItems(std::move(selectedItems));
finishSelectionAnimations();
}
void TopBar::setTitle(rpl::producer<QString> &&title) {
2017-11-26 17:05:52 +00:00
if (_title) {
delete _title;
}
_title = Ui::CreateChild<Ui::FadeWrapScaled<Ui::FlatLabel>>(
this,
object_ptr<Ui::FlatLabel>(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());
}
2017-11-26 17:05:52 +00:00
void TopBar::enableBackButton() {
if (_back) {
return;
}
2017-11-26 17:05:52 +00:00
_back = Ui::CreateChild<Ui::FadeWrapScaled<Ui::IconButton>>(
this,
object_ptr<Ui::IconButton>(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) {
2017-11-26 17:05:52 +00:00
_title->setAttribute(Qt::WA_TransparentForMouseEvents);
}
updateControlsGeometry(width());
}
2017-11-03 12:03:00 +00:00
void TopBar::createSearchView(
not_null<Ui::SearchFieldController*> controller,
rpl::producer<bool> &&shown) {
setSearchField(
controller->createField(this, _st.searchRow.field),
std::move(shown));
2017-11-03 12:03:00 +00:00
}
void TopBar::pushButton(base::unique_qptr<Ui::RpWidget> button) {
auto weak = button.get();
_buttons.push_back(std::move(button));
2017-11-03 12:03:00 +00:00
weak->setParent(this);
weak->widthValue()
| rpl::start_with_next([this] {
2017-11-03 12:03:00 +00:00
updateControlsGeometry(width());
}, lifetime());
}
2017-11-03 12:03:00 +00:00
void TopBar::setSearchField(
base::unique_qptr<Ui::InputField> field,
rpl::producer<bool> &&shown) {
2017-11-03 12:03:00 +00:00
if (auto value = field.release()) {
createSearchView(value, std::move(shown));
2017-11-03 12:03:00 +00:00
} else {
_searchView = nullptr;
}
}
void TopBar::createSearchView(
not_null<Ui::InputField*> field,
rpl::producer<bool> &&shown) {
2017-11-03 12:03:00 +00:00
_searchView = base::make_unique_q<Ui::FixedHeightWidget>(
this,
_st.searchRow.height);
auto wrap = _searchView.get();
field->setParent(wrap);
auto search = addButton(
2017-11-26 17:05:52 +00:00
base::make_unique_q<Ui::FadeWrapScaled<Ui::FadeWrapScaled<Ui::IconButton>>>(
2017-11-03 12:03:00 +00:00
this,
2017-11-26 17:05:52 +00:00
object_ptr<Ui::FadeWrapScaled<Ui::IconButton>>(
this,
object_ptr<Ui::IconButton>(this, _st.search))));
_defaultControls.push_back(search);
auto cancel = Ui::CreateChild<Ui::FadeWrapScaled<Ui::CrossButton>>(
2017-11-03 12:03:00 +00:00
wrap,
2017-11-26 17:05:52 +00:00
object_ptr<Ui::CrossButton>(wrap, _st.searchRow.fieldCancel));
_defaultControls.push_back(cancel);
2017-11-03 12:03:00 +00:00
auto toggleSearchMode = [=](bool enabled, anim::type animated) {
if (!enabled) {
setFocus();
}
2017-11-03 12:03:00 +00:00
if (_title) {
2017-11-26 17:05:52 +00:00
_title->entity()->setVisible(!enabled);
2017-11-03 12:03:00 +00:00
}
field->setVisible(enabled);
2017-11-26 17:05:52 +00:00
cancel->entity()->toggleAnimated(enabled);
2017-11-03 12:03:00 +00:00
if (animated == anim::type::instant) {
2017-11-26 17:05:52 +00:00
cancel->entity()->finishAnimations();
2017-11-03 12:03:00 +00:00
}
2017-11-26 17:05:52 +00:00
search->wrapped()->toggle(!enabled, animated);
if (enabled) {
field->setFocus();
}
2017-11-03 12:03:00 +00:00
};
auto cancelSearch = [=] {
if (!field->getLastText().isEmpty()) {
field->setText(QString());
} else {
toggleSearchMode(false, anim::type::normal);
}
};
2017-11-26 17:05:52 +00:00
cancel->entity()->addClickHandler(cancelSearch);
2017-11-03 12:03:00 +00:00
field->connect(field, &Ui::InputField::cancelled, cancelSearch);
wrap->widthValue()
| rpl::start_with_next([=](int newWidth) {
auto availableWidth = newWidth
- _st.searchRow.fieldCancelSkip;
field->setGeometryToLeft(
_st.searchRow.padding.left(),
_st.searchRow.padding.top(),
availableWidth,
field->height());
cancel->moveToRight(0, 0);
}, wrap->lifetime());
widthValue()
| rpl::start_with_next([=](int newWidth) {
auto left = _back
? _st.back.width
: _st.titlePosition.x();
wrap->setGeometryToLeft(
left,
0,
newWidth - left,
wrap->height(),
newWidth);
}, wrap->lifetime());
search->entity()->addClickHandler([=] {
toggleSearchMode(true, anim::type::normal);
});
field->alive()
| rpl::start_with_done([=] {
field->setParent(nullptr);
removeButton(search);
setSearchField(nullptr, rpl::never<bool>());
2017-11-03 12:03:00 +00:00
}, _searchView->lifetime());
toggleSearchMode(
!field->getLastText().isEmpty(),
anim::type::instant);
std::move(shown)
| rpl::start_with_next([=](bool visible) {
if (!field->getLastText().isEmpty()) {
return;
}
toggleSearchMode(false, anim::type::instant);
wrap->setVisible(visible);
2017-11-26 17:05:52 +00:00
search->wrapped()->toggle(visible, anim::type::instant);
}, wrap->lifetime());
2017-11-03 12:03:00 +00:00
}
void TopBar::removeButton(not_null<Ui::RpWidget*> button) {
_buttons.erase(
std::remove(_buttons.begin(), _buttons.end(), button),
_buttons.end());
}
int TopBar::resizeGetHeight(int newWidth) {
updateControlsGeometry(newWidth);
return _st.height;
}
2017-11-26 17:05:52 +00:00
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) {
2017-11-26 17:05:52 +00:00
updateDefaultControlsGeometry(newWidth);
updateSelectionControlsGeometry(newWidth);
}
void TopBar::updateDefaultControlsGeometry(int newWidth) {
auto right = 0;
for (auto &button : _buttons) {
2017-11-03 12:03:00 +00:00
if (!button) continue;
button->moveToRight(right, 0, newWidth);
right += button->width();
}
if (_back) {
_back->setGeometryToLeft(
0,
0,
newWidth - right,
_back->height(),
newWidth);
}
if (_title) {
_title->moveToLeft(
_back ? _st.back.width : _st.titlePosition.x(),
_st.titlePosition.y(),
newWidth);
}
}
2017-11-26 17:05:52 +00:00
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);
2017-11-16 09:13:17 +00:00
auto ms = getms();
auto highlight = _a_highlight.current(ms, _highlight ? 1. : 0.);
if (_highlight && !_a_highlight.animating()) {
_highlight = false;
startHighlightAnimation();
}
auto brush = anim::brush(_st.bg, _st.highlightBg, highlight);
p.fillRect(e->rect(), brush);
}
void TopBar::highlight() {
_highlight = true;
startHighlightAnimation();
}
void TopBar::startHighlightAnimation() {
_a_highlight.start(
[this] { update(); },
_highlight ? 0. : 1.,
_highlight ? 1. : 0.,
_st.highlightDuration);
}
2017-11-26 17:05:52 +00:00
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<Ui::FadeWrapScaled<Ui::IconButton>>(
this,
object_ptr<Ui::IconButton>(this, _st.mediaCancel)));
_cancelSelection->entity()->clicks()
| rpl::start_to_stream(
_cancelSelectionClicks,
_cancelSelection->lifetime());
_selectionText = wrap(Ui::CreateChild<Ui::FadeWrapScaled<Ui::LabelWithNumbers>>(
this,
object_ptr<Ui::LabelWithNumbers>(
this,
_st.title,
_st.titlePosition.y(),
generateSelectedText())));
_selectionText->entity()->resize(0, _st.height);
_forward = wrap(Ui::CreateChild<Ui::FadeWrapScaled<Ui::IconButton>>(
this,
object_ptr<Ui::IconButton>(this, _st.mediaForward)));
_forward->entity()->addClickHandler([this] { performForward(); });
_delete = wrap(Ui::CreateChild<Ui::FadeWrapScaled<Ui::IconButton>>(
this,
object_ptr<Ui::IconButton>(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<Data>;
case Type::Video: return lng_media_selected_video__generic<Data>;
case Type::File: return lng_media_selected_file__generic<Data>;
case Type::MusicFile: return lng_media_selected_song__generic<Data>;
case Type::Link: return lng_media_selected_link__generic<Data>;
case Type::VoiceFile: return lng_media_selected_audio__generic<Data>;
// case Type::RoundFile: return lng_media_selected_round__generic<Data>;
}
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<PeerData*> peer) {
App::main()->setForwardDraft(peer->id, items);
if (that) {
that->_cancelSelectionClicks.fire({});
}
};
Ui::show(Box<PeerListBox>(
std::make_unique<ChooseRecipientBoxController>(std::move(callback)),
[](not_null<PeerListBox*> box) {
box->addButton(langFactory(lng_cancel), [box] {
box->closeBox();
});
}));
}
void TopBar::performDelete() {
auto items = collectItems();
if (items.empty()) {
_cancelSelectionClicks.fire({});
} else {
Ui::show(Box<DeleteMessagesBox>(items));
}
}
rpl::producer<QString> TitleValue(
const Section &section,
not_null<PeerData*> peer) {
return Lang::Viewer([&] {
switch (section.type()) {
case Section::Type::Profile:
if (auto user = peer->asUser()) {
return user->botInfo
? lng_info_bot_title
: lng_info_user_title;
} else if (auto channel = peer->asChannel()) {
return channel->isMegagroup()
? lng_info_group_title
: lng_info_channel_title;
} else if (peer->isChat()) {
return lng_info_group_title;
}
Unexpected("Bad peer type in Info::TitleValue()");
case Section::Type::Media:
switch (section.mediaType()) {
case Section::MediaType::Photo:
return lng_media_type_photos;
case Section::MediaType::Video:
return lng_media_type_videos;
case Section::MediaType::MusicFile:
return lng_media_type_songs;
case Section::MediaType::File:
return lng_media_type_files;
case Section::MediaType::VoiceFile:
return lng_media_type_audios;
case Section::MediaType::Link:
return lng_media_type_links;
case Section::MediaType::RoundFile:
return lng_media_type_rounds;
}
Unexpected("Bad media type in Info::TitleValue()");
case Section::Type::CommonGroups:
return lng_profile_common_groups_section;
case Section::Type::Members:
return lng_profile_participants_section;
}
Unexpected("Bad section type in Info::TitleValue()");
}());
}
} // namespace Info