tdesktop/Telegram/SourceFiles/info/info_top_bar.cpp

747 lines
20 KiB
C++

/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "info/info_top_bar.h"
#include "dialogs/ui/dialogs_stories_list.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/delete_messages_box.h"
#include "boxes/peer_list_controllers.h"
#include "mainwidget.h"
#include "main/main_session.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/widgets/shadow.h"
#include "ui/wrap/fade_wrap.h"
#include "ui/wrap/padding_wrap.h"
#include "ui/search_field_controller.h"
#include "window/window_peer_menu.h"
#include "data/data_session.h"
#include "data/data_channel.h"
#include "data/data_user.h"
#include "styles/style_dialogs.h"
#include "styles/style_info.h"
namespace Info {
TopBar::TopBar(
QWidget *parent,
not_null<Window::SessionNavigation*> navigation,
const style::InfoTopBar &st,
SelectedItems &&selectedItems)
: RpWidget(parent)
, _navigation(navigation)
, _st(st)
, _selectedItems(Section::MediaType::kCount) {
if (_st.radius) {
_roundRect.emplace(_st.radius, _st.bg);
}
setAttribute(Qt::WA_OpaquePaintEvent, !_roundRect);
setSelectedItems(std::move(selectedItems));
updateControlsVisibility(anim::type::instant);
}
template <typename Callback>
void TopBar::registerUpdateControlCallback(
QObject *guard,
Callback &&callback) {
_updateControlCallbacks[guard] =[
weak = Ui::MakeWeak(guard),
callback = std::forward<Callback>(callback)
](anim::type animated) {
if (!weak) {
return false;
}
callback(animated);
return true;
};
}
template <typename Widget, typename IsVisible>
void TopBar::registerToggleControlCallback(
Widget *widget,
IsVisible &&callback) {
registerUpdateControlCallback(widget, [
widget,
isVisible = std::forward<IsVisible>(callback)
](anim::type animated) {
widget->toggle(isVisible(), animated);
});
}
void TopBar::setTitle(TitleDescriptor descriptor) {
if (_title) {
delete _title;
}
if (_subtitle) {
delete _subtitle;
}
const auto withSubtitle = !!descriptor.subtitle;
if (withSubtitle) {
_subtitle = Ui::CreateChild<Ui::FadeWrap<Ui::FlatLabel>>(
this,
object_ptr<Ui::FlatLabel>(
this,
std::move(descriptor.subtitle),
_st.subtitle),
st::infoTopBarScale);
_subtitle->setDuration(st::infoTopBarDuration);
_subtitle->toggle(
!selectionMode() && !storiesTitle(),
anim::type::instant);
registerToggleControlCallback(_subtitle.data(), [=] {
return !selectionMode() && !storiesTitle() && !searchMode();
});
}
_title = Ui::CreateChild<Ui::FadeWrap<Ui::FlatLabel>>(
this,
object_ptr<Ui::FlatLabel>(
this,
std::move(descriptor.title),
withSubtitle ? _st.titleWithSubtitle : _st.title),
st::infoTopBarScale);
_title->setDuration(st::infoTopBarDuration);
_title->toggle(
!selectionMode() && !storiesTitle(),
anim::type::instant);
registerToggleControlCallback(_title.data(), [=] {
return !selectionMode() && !storiesTitle() && !searchMode();
});
if (_back) {
_title->setAttribute(Qt::WA_TransparentForMouseEvents);
if (_subtitle) {
_subtitle->setAttribute(Qt::WA_TransparentForMouseEvents);
}
}
updateControlsGeometry(width());
}
void TopBar::enableBackButton() {
if (_back) {
return;
}
_back = Ui::CreateChild<Ui::FadeWrap<Ui::IconButton>>(
this,
object_ptr<Ui::IconButton>(this, _st.back),
st::infoTopBarScale);
_back->setDuration(st::infoTopBarDuration);
_back->toggle(!selectionMode(), anim::type::instant);
_back->entity()->clicks(
) | rpl::to_empty
| rpl::start_to_stream(_backClicks, _back->lifetime());
registerToggleControlCallback(_back.data(), [=] {
return !selectionMode();
});
if (_title) {
_title->setAttribute(Qt::WA_TransparentForMouseEvents);
}
if (_subtitle) {
_subtitle->setAttribute(Qt::WA_TransparentForMouseEvents);
}
if (_storiesWrap) {
_storiesWrap->raise();
}
updateControlsGeometry(width());
}
void TopBar::createSearchView(
not_null<Ui::SearchFieldController*> controller,
rpl::producer<bool> &&shown,
bool startsFocused) {
setSearchField(
controller->createField(this, _st.searchRow.field),
std::move(shown),
startsFocused);
}
bool TopBar::focusSearchField() {
if (_searchField && _searchField->isVisible()) {
_searchField->setFocus();
return true;
}
return false;
}
Ui::FadeWrap<Ui::RpWidget> *TopBar::pushButton(
base::unique_qptr<Ui::RpWidget> button) {
auto wrapped = base::make_unique_q<Ui::FadeWrap<Ui::RpWidget>>(
this,
object_ptr<Ui::RpWidget>::fromRaw(button.release()),
st::infoTopBarScale);
auto weak = wrapped.get();
_buttons.push_back(std::move(wrapped));
weak->setDuration(st::infoTopBarDuration);
registerToggleControlCallback(weak, [=] {
return !selectionMode()
&& !_searchModeEnabled;
});
weak->toggle(
!selectionMode() && !_searchModeEnabled,
anim::type::instant);
weak->widthValue(
) | rpl::start_with_next([this] {
updateControlsGeometry(width());
}, lifetime());
return weak;
}
void TopBar::forceButtonVisibility(
Ui::FadeWrap<Ui::RpWidget> *button,
rpl::producer<bool> shown) {
_updateControlCallbacks.erase(button);
button->toggleOn(std::move(shown));
}
void TopBar::setSearchField(
base::unique_qptr<Ui::InputField> field,
rpl::producer<bool> &&shown,
bool startsFocused) {
Expects(field != nullptr);
createSearchView(field.release(), std::move(shown), startsFocused);
}
void TopBar::clearSearchField() {
_searchView = nullptr;
}
void TopBar::createSearchView(
not_null<Ui::InputField*> field,
rpl::producer<bool> &&shown,
bool startsFocused) {
_searchView = base::make_unique_q<Ui::FixedHeightWidget>(
this,
_st.searchRow.height);
auto wrap = _searchView.get();
registerUpdateControlCallback(wrap, [=](anim::type) {
wrap->setVisible(!selectionMode() && _searchModeAvailable);
});
_searchField = field;
auto fieldWrap = Ui::CreateChild<Ui::FadeWrap<Ui::InputField>>(
wrap,
object_ptr<Ui::InputField>::fromRaw(field),
st::infoTopBarScale);
fieldWrap->setDuration(st::infoTopBarDuration);
auto focusLifetime = field->lifetime().make_state<rpl::lifetime>();
registerUpdateControlCallback(fieldWrap, [=](anim::type animated) {
auto fieldShown = !selectionMode() && searchMode();
if (!fieldShown && field->hasFocus()) {
setFocus();
}
fieldWrap->toggle(fieldShown, animated);
if (fieldShown) {
*focusLifetime = field->shownValue()
| rpl::filter([](bool shown) { return shown; })
| rpl::take(1)
| rpl::start_with_next([=] { field->setFocus(); });
} else {
focusLifetime->destroy();
}
});
auto button = base::make_unique_q<Ui::IconButton>(this, _st.search);
auto search = button.get();
search->addClickHandler([=] { showSearch(); });
auto searchWrap = pushButton(std::move(button));
registerToggleControlCallback(searchWrap, [=] {
return !selectionMode()
&& _searchModeAvailable
&& !_searchModeEnabled;
});
auto cancel = Ui::CreateChild<Ui::CrossButton>(
wrap,
_st.searchRow.fieldCancel);
registerToggleControlCallback(cancel, [=] {
return !selectionMode() && searchMode();
});
auto cancelSearch = [=] {
if (!field->getLastText().isEmpty()) {
field->setText(QString());
} else {
_searchModeEnabled = false;
updateControlsVisibility(anim::type::normal);
}
};
cancel->addClickHandler(cancelSearch);
field->cancelled(
) | rpl::start_with_next(cancelSearch, field->lifetime());
wrap->widthValue(
) | rpl::start_with_next([=](int newWidth) {
auto availableWidth = newWidth
- _st.searchRow.fieldCancelSkip;
fieldWrap->resizeToWidth(availableWidth);
fieldWrap->moveToLeft(
_st.searchRow.padding.left(),
_st.searchRow.padding.top());
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());
field->alive(
) | rpl::start_with_done([=] {
field->setParent(nullptr);
removeButton(search);
clearSearchField();
}, _searchView->lifetime());
_searchModeEnabled = !field->getLastText().isEmpty() || startsFocused;
updateControlsVisibility(anim::type::instant);
std::move(
shown
) | rpl::start_with_next([=](bool visible) {
auto alreadyInSearch = !field->getLastText().isEmpty();
_searchModeAvailable = visible || alreadyInSearch;
updateControlsVisibility(anim::type::instant);
}, wrap->lifetime());
}
void TopBar::showSearch() {
_searchModeEnabled = true;
updateControlsVisibility(anim::type::normal);
}
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;
}
void TopBar::updateControlsGeometry(int newWidth) {
updateDefaultControlsGeometry(newWidth);
updateSelectionControlsGeometry(newWidth);
updateStoriesGeometry(newWidth);
}
void TopBar::updateDefaultControlsGeometry(int newWidth) {
auto right = 0;
for (auto &button : _buttons) {
if (!button) {
continue;
}
button->moveToRight(right, 0, newWidth);
right += button->width();
}
if (_back) {
_back->setGeometryToLeft(
0,
0,
newWidth - right,
_back->height(),
newWidth);
}
if (_title) {
const auto x = _back
? _st.back.width
: _subtitle
? _st.titleWithSubtitlePosition.x()
: _st.titlePosition.x();
const auto y = _subtitle
? _st.titleWithSubtitlePosition.y()
: _st.titlePosition.y();
_title->moveToLeft(x, y, newWidth);
if (_subtitle) {
_subtitle->moveToLeft(
_back ? _st.back.width : _st.subtitlePosition.x(),
_st.subtitlePosition.y(),
newWidth);
}
}
}
void TopBar::updateSelectionControlsGeometry(int newWidth) {
if (!_selectionText) {
return;
}
auto right = _st.mediaActionsSkip;
if (_canDelete) {
_delete->moveToRight(right, 0, newWidth);
right += _delete->width();
}
if (_canToggleStoryPin) {
_toggleStoryPin->moveToRight(right, 0, newWidth);
right += _toggleStoryPin->width();
}
if (_canForward) {
_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->resizeToNaturalWidth(availableWidth);
_selectionText->moveToLeft(
left,
top,
newWidth);
}
void TopBar::updateStoriesGeometry(int newWidth) {
if (!_stories) {
return;
}
auto right = 0;
for (auto &button : _buttons) {
if (!button) {
continue;
}
button->moveToRight(right, 0, newWidth);
right += button->width();
}
const auto &small = st::dialogsStories;
const auto wrapLeft = (_back ? _st.back.width : 0);
const auto left = _back
? 0
: (_st.titlePosition.x() - small.left - small.photoLeft);
const auto height = small.photo + 2 * small.photoTop;
const auto top = _st.titlePosition.y()
+ (_st.title.style.font->height - height) / 2;
_stories->setLayoutConstraints({ left, top }, style::al_left);
_storiesWrap->move(wrapLeft, 0);
}
void TopBar::paintEvent(QPaintEvent *e) {
auto p = QPainter(this);
auto highlight = _a_highlight.value(_highlight ? 1. : 0.);
if (_highlight && !_a_highlight.animating()) {
_highlight = false;
startHighlightAnimation();
}
if (!_roundRect) {
const auto brush = anim::brush(_st.bg, _st.highlightBg, highlight);
p.fillRect(e->rect(), brush);
} else if (highlight > 0.) {
p.setPen(Qt::NoPen);
p.setBrush(anim::brush(_st.bg, _st.highlightBg, highlight));
p.drawRoundedRect(
rect() + style::margins(0, 0, 0, _st.radius * 2),
_st.radius,
_st.radius);
} else {
_roundRect->paintSomeRounded(
p,
rect(),
RectPart::TopLeft | RectPart::TopRight);
}
}
void TopBar::highlight() {
_highlight = true;
startHighlightAnimation();
}
void TopBar::startHighlightAnimation() {
_a_highlight.start(
[this] { update(); },
_highlight ? 0. : 1.,
_highlight ? 1. : 0.,
_st.highlightDuration);
}
void TopBar::updateControlsVisibility(anim::type animated) {
for (auto i = _updateControlCallbacks.begin(); i != _updateControlCallbacks.end();) {
auto &&[widget, callback] = *i;
if (!callback(animated)) {
i = _updateControlCallbacks.erase(i);
} else {
++i;
}
}
}
void TopBar::setStories(rpl::producer<Dialogs::Stories::Content> content) {
_storiesLifetime.destroy();
delete _storiesWrap.data();
if (content) {
using namespace Dialogs::Stories;
auto last = std::move(
content
) | rpl::start_spawning(_storiesLifetime);
_storiesWrap = _storiesLifetime.make_state<
Ui::FadeWrap<Ui::AbstractButton>
>(this, object_ptr<Ui::AbstractButton>(this), st::infoTopBarScale);
registerToggleControlCallback(
_storiesWrap.data(),
[this] { return _storiesCount > 0; });
_storiesWrap->toggle(false, anim::type::instant);
_storiesWrap->setDuration(st::infoTopBarDuration);
const auto button = _storiesWrap->entity();
const auto stories = Ui::CreateChild<List>(
button,
st::dialogsStoriesListInfo,
rpl::duplicate(
last
) | rpl::filter([](const Content &content) {
return !content.elements.empty();
}));
const auto label = Ui::CreateChild<Ui::FlatLabel>(
button,
QString(),
_st.title);
stories->setAttribute(Qt::WA_TransparentForMouseEvents);
label->setAttribute(Qt::WA_TransparentForMouseEvents);
stories->geometryValue(
) | rpl::start_with_next([=](QRect geometry) {
const auto skip = _st.title.style.font->spacew;
label->move(
geometry.x() + geometry.width() + skip,
_st.titlePosition.y());
}, label->lifetime());
rpl::combine(
_storiesWrap->positionValue(),
label->geometryValue()
) | rpl::start_with_next([=] {
button->resize(
label->x() + label->width() + _st.titlePosition.x(),
_st.height);
}, button->lifetime());
_stories = stories;
_stories->clicks(
) | rpl::start_to_stream(_storyClicks, _stories->lifetime());
button->setClickedCallback([=] {
_storyClicks.fire({});
});
rpl::duplicate(
last
) | rpl::start_with_next([=](const Content &content) {
const auto count = content.total;
if (_storiesCount != count) {
const auto was = (_storiesCount > 0);
_storiesCount = count;
const auto now = (_storiesCount > 0);
if (was != now) {
updateControlsVisibility(anim::type::normal);
}
if (now) {
label->setText(
tr::lng_contacts_stories_status(
tr::now,
lt_count,
_storiesCount));
}
updateControlsGeometry(width());
}
}, _storiesLifetime);
_storiesLifetime.add([weak = QPointer<QWidget>(label)] {
delete weak.data();
});
} else {
_storiesCount = 0;
}
updateControlsVisibility(anim::type::instant);
}
void TopBar::setStoriesArchive(bool archive) {
_storiesArchive = archive;
}
void TopBar::setSelectedItems(SelectedItems &&items) {
auto wasSelectionMode = selectionMode();
_selectedItems = std::move(items);
if (selectionMode()) {
if (_selectionText) {
updateSelectionState();
if (!wasSelectionMode) {
_selectionText->entity()->finishAnimating();
}
} else {
createSelectionControls();
}
}
updateControlsVisibility(anim::type::normal);
}
SelectedItems TopBar::takeSelectedItems() {
_canDelete = false;
_canForward = false;
return std::move(_selectedItems);
}
rpl::producer<SelectionAction> TopBar::selectionActionRequests() const {
return _selectionActionRequests.events();
}
void TopBar::updateSelectionState() {
Expects(_selectionText && _delete && _forward && _toggleStoryPin);
_canDelete = computeCanDelete();
_canForward = computeCanForward();
_selectionText->entity()->setValue(generateSelectedText());
_delete->toggle(_canDelete, anim::type::instant);
_forward->toggle(_canForward, anim::type::instant);
_toggleStoryPin->toggle(_canToggleStoryPin, anim::type::instant);
updateSelectionControlsGeometry(width());
}
void TopBar::createSelectionControls() {
auto wrap = [&](auto created) {
registerToggleControlCallback(
created,
[this] { return selectionMode(); });
created->toggle(false, anim::type::instant);
return created;
};
_canDelete = computeCanDelete();
_canForward = computeCanForward();
_canToggleStoryPin = computeCanToggleStoryPin();
_cancelSelection = wrap(Ui::CreateChild<Ui::FadeWrap<Ui::IconButton>>(
this,
object_ptr<Ui::IconButton>(this, _st.mediaCancel),
st::infoTopBarScale));
_cancelSelection->setDuration(st::infoTopBarDuration);
_cancelSelection->entity()->clicks(
) | rpl::map_to(
SelectionAction::Clear
) | rpl::start_to_stream(
_selectionActionRequests,
_cancelSelection->lifetime());
_selectionText = wrap(Ui::CreateChild<Ui::FadeWrap<Ui::LabelWithNumbers>>(
this,
object_ptr<Ui::LabelWithNumbers>(
this,
_st.title,
_st.titlePosition.y(),
generateSelectedText()),
st::infoTopBarScale));
_selectionText->setDuration(st::infoTopBarDuration);
_selectionText->entity()->resize(0, _st.height);
_forward = wrap(Ui::CreateChild<Ui::FadeWrap<Ui::IconButton>>(
this,
object_ptr<Ui::IconButton>(this, _st.mediaForward),
st::infoTopBarScale));
registerToggleControlCallback(
_forward.data(),
[this] { return selectionMode() && _canForward; });
_forward->setDuration(st::infoTopBarDuration);
_forward->entity()->clicks(
) | rpl::map_to(
SelectionAction::Forward
) | rpl::start_to_stream(
_selectionActionRequests,
_cancelSelection->lifetime());
_forward->entity()->setVisible(_canForward);
_delete = wrap(Ui::CreateChild<Ui::FadeWrap<Ui::IconButton>>(
this,
object_ptr<Ui::IconButton>(this, _st.mediaDelete),
st::infoTopBarScale));
registerToggleControlCallback(
_delete.data(),
[this] { return selectionMode() && _canDelete; });
_delete->setDuration(st::infoTopBarDuration);
_delete->entity()->clicks(
) | rpl::map_to(
SelectionAction::Delete
) | rpl::start_to_stream(
_selectionActionRequests,
_cancelSelection->lifetime());
_delete->entity()->setVisible(_canDelete);
const auto archive =
_toggleStoryPin = wrap(Ui::CreateChild<Ui::FadeWrap<Ui::IconButton>>(
this,
object_ptr<Ui::IconButton>(
this,
_storiesArchive ? _st.storiesSave : _st.storiesArchive),
st::infoTopBarScale));
registerToggleControlCallback(
_toggleStoryPin.data(),
[this] { return selectionMode() && _canToggleStoryPin; });
_toggleStoryPin->setDuration(st::infoTopBarDuration);
_toggleStoryPin->entity()->clicks(
) | rpl::map_to(
SelectionAction::ToggleStoryPin
) | rpl::start_to_stream(
_selectionActionRequests,
_cancelSelection->lifetime());
_toggleStoryPin->entity()->setVisible(_canToggleStoryPin);
updateControlsGeometry(width());
}
bool TopBar::computeCanDelete() const {
return ranges::all_of(_selectedItems.list, &SelectedItem::canDelete);
}
bool TopBar::computeCanForward() const {
return ranges::all_of(_selectedItems.list, &SelectedItem::canForward);
}
bool TopBar::computeCanToggleStoryPin() const {
return ranges::all_of(
_selectedItems.list,
&SelectedItem::canToggleStoryPin);
}
Ui::StringWithNumbers TopBar::generateSelectedText() const {
return _selectedItems.title(_selectedItems.list.size());
}
bool TopBar::selectionMode() const {
return !_selectedItems.list.empty();
}
bool TopBar::storiesTitle() const {
return _storiesCount > 0;
}
bool TopBar::searchMode() const {
return _searchModeAvailable && _searchModeEnabled;
}
void TopBar::performForward() {
_selectionActionRequests.fire(SelectionAction::Forward);
}
void TopBar::performDelete() {
_selectionActionRequests.fire(SelectionAction::Delete);
}
} // namespace Info