2017-09-13 17:01:23 +00:00
|
|
|
/*
|
|
|
|
This file is part of Telegram Desktop,
|
2018-01-03 10:23:14 +00:00
|
|
|
the official desktop application for the Telegram messaging service.
|
2017-09-13 17:01:23 +00:00
|
|
|
|
2018-01-03 10:23:14 +00:00
|
|
|
For license and copyright information please follow this link:
|
|
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
2017-09-13 17:01:23 +00:00
|
|
|
*/
|
|
|
|
#include "info/info_top_bar.h"
|
|
|
|
|
2017-11-03 18:26:14 +00:00
|
|
|
#include <rpl/never.h>
|
2017-11-26 17:05:52 +00:00
|
|
|
#include <rpl/merge.h>
|
2023-06-20 15:31:40 +00:00
|
|
|
#include "dialogs/ui/dialogs_stories_content.h"
|
|
|
|
#include "dialogs/ui/dialogs_stories_list.h"
|
2017-09-30 18:26:45 +00:00
|
|
|
#include "lang/lang_keys.h"
|
2019-09-17 07:51:02 +00:00
|
|
|
#include "lang/lang_numbers_animation.h"
|
2017-09-30 18:26:45 +00:00
|
|
|
#include "info/info_wrap_widget.h"
|
2017-10-31 18:25:22 +00:00
|
|
|
#include "info/info_controller.h"
|
2017-11-17 13:23:36 +00:00
|
|
|
#include "info/profile/info_profile_values.h"
|
2017-09-30 18:26:45 +00:00
|
|
|
#include "storage/storage_shared_media.h"
|
2021-10-18 20:55:58 +00:00
|
|
|
#include "boxes/delete_messages_box.h"
|
2017-11-26 17:05:52 +00:00
|
|
|
#include "boxes/peer_list_controllers.h"
|
|
|
|
#include "mainwidget.h"
|
2019-07-24 11:45:24 +00:00
|
|
|
#include "main/main_session.h"
|
2017-09-13 17:01:23 +00:00
|
|
|
#include "ui/widgets/buttons.h"
|
|
|
|
#include "ui/widgets/labels.h"
|
2023-08-31 11:21:24 +00:00
|
|
|
#include "ui/widgets/fields/input_field.h"
|
2017-09-30 18:26:45 +00:00
|
|
|
#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"
|
2017-12-06 10:13:38 +00:00
|
|
|
#include "window/window_peer_menu.h"
|
2019-04-25 12:45:15 +00:00
|
|
|
#include "data/data_session.h"
|
2019-01-04 11:09:48 +00:00
|
|
|
#include "data/data_channel.h"
|
|
|
|
#include "data/data_user.h"
|
2023-06-20 15:31:40 +00:00
|
|
|
#include "styles/style_dialogs.h"
|
2019-01-04 11:09:48 +00:00
|
|
|
#include "styles/style_info.h"
|
2017-09-13 17:01:23 +00:00
|
|
|
|
|
|
|
namespace Info {
|
|
|
|
|
2017-11-26 17:05:52 +00:00
|
|
|
TopBar::TopBar(
|
|
|
|
QWidget *parent,
|
2019-07-25 18:55:11 +00:00
|
|
|
not_null<Window::SessionNavigation*> navigation,
|
2017-11-26 17:05:52 +00:00
|
|
|
const style::InfoTopBar &st,
|
|
|
|
SelectedItems &&selectedItems)
|
2017-09-13 17:01:23 +00:00
|
|
|
: RpWidget(parent)
|
2019-07-25 18:55:11 +00:00
|
|
|
, _navigation(navigation)
|
2017-11-26 17:05:52 +00:00
|
|
|
, _st(st)
|
|
|
|
, _selectedItems(Section::MediaType::kCount) {
|
2022-02-08 17:25:24 +00:00
|
|
|
if (_st.radius) {
|
|
|
|
_roundRect.emplace(_st.radius, _st.bg);
|
|
|
|
}
|
|
|
|
setAttribute(Qt::WA_OpaquePaintEvent, !_roundRect);
|
2017-11-26 17:05:52 +00:00
|
|
|
setSelectedItems(std::move(selectedItems));
|
2017-11-27 11:43:57 +00:00
|
|
|
updateControlsVisibility(anim::type::instant);
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename Callback>
|
|
|
|
void TopBar::registerUpdateControlCallback(
|
|
|
|
QObject *guard,
|
|
|
|
Callback &&callback) {
|
|
|
|
_updateControlCallbacks[guard] =[
|
2019-09-13 12:22:54 +00:00
|
|
|
weak = Ui::MakeWeak(guard),
|
2017-11-27 11:43:57 +00:00
|
|
|
callback = std::forward<Callback>(callback)
|
|
|
|
](anim::type animated) {
|
2017-11-30 18:04:13 +00:00
|
|
|
if (!weak) {
|
2017-11-27 11:43:57 +00:00
|
|
|
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);
|
|
|
|
});
|
2017-09-13 17:01:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void TopBar::setTitle(rpl::producer<QString> &&title) {
|
2017-11-26 17:05:52 +00:00
|
|
|
if (_title) {
|
|
|
|
delete _title;
|
|
|
|
}
|
2017-11-27 11:43:57 +00:00
|
|
|
_title = Ui::CreateChild<Ui::FadeWrap<Ui::FlatLabel>>(
|
2017-11-26 17:05:52 +00:00
|
|
|
this,
|
2017-11-27 11:43:57 +00:00
|
|
|
object_ptr<Ui::FlatLabel>(this, std::move(title), _st.title),
|
|
|
|
st::infoTopBarScale);
|
|
|
|
_title->setDuration(st::infoTopBarDuration);
|
2023-06-20 15:31:40 +00:00
|
|
|
_title->toggle(
|
|
|
|
!selectionMode() && !storiesTitle(),
|
|
|
|
anim::type::instant);
|
2017-11-27 11:43:57 +00:00
|
|
|
registerToggleControlCallback(_title.data(), [=] {
|
2023-06-20 15:31:40 +00:00
|
|
|
return !selectionMode() && !storiesTitle() && !searchMode();
|
2017-11-27 11:43:57 +00:00
|
|
|
});
|
2017-11-26 17:05:52 +00:00
|
|
|
|
2017-09-15 17:34:41 +00:00
|
|
|
if (_back) {
|
|
|
|
_title->setAttribute(Qt::WA_TransparentForMouseEvents);
|
|
|
|
}
|
|
|
|
updateControlsGeometry(width());
|
2017-09-13 17:01:23 +00:00
|
|
|
}
|
|
|
|
|
2017-11-26 17:05:52 +00:00
|
|
|
void TopBar::enableBackButton() {
|
|
|
|
if (_back) {
|
|
|
|
return;
|
2017-09-13 17:01:23 +00:00
|
|
|
}
|
2017-11-27 11:43:57 +00:00
|
|
|
_back = Ui::CreateChild<Ui::FadeWrap<Ui::IconButton>>(
|
2017-11-26 17:05:52 +00:00
|
|
|
this,
|
2017-11-27 11:43:57 +00:00
|
|
|
object_ptr<Ui::IconButton>(this, _st.back),
|
|
|
|
st::infoTopBarScale);
|
|
|
|
_back->setDuration(st::infoTopBarDuration);
|
2017-11-26 17:05:52 +00:00
|
|
|
_back->toggle(!selectionMode(), anim::type::instant);
|
2018-09-13 20:09:26 +00:00
|
|
|
_back->entity()->clicks(
|
2020-06-21 16:25:29 +00:00
|
|
|
) | rpl::to_empty
|
|
|
|
| rpl::start_to_stream(_backClicks, _back->lifetime());
|
2017-11-27 11:43:57 +00:00
|
|
|
registerToggleControlCallback(_back.data(), [=] {
|
|
|
|
return !selectionMode();
|
|
|
|
});
|
2017-11-26 17:05:52 +00:00
|
|
|
|
2017-09-13 17:01:23 +00:00
|
|
|
if (_title) {
|
2017-11-26 17:05:52 +00:00
|
|
|
_title->setAttribute(Qt::WA_TransparentForMouseEvents);
|
2017-09-13 17:01:23 +00:00
|
|
|
}
|
2023-07-13 08:39:18 +00:00
|
|
|
if (_storiesWrap) {
|
|
|
|
_storiesWrap->raise();
|
|
|
|
}
|
2017-09-13 17:01:23 +00:00
|
|
|
updateControlsGeometry(width());
|
|
|
|
}
|
|
|
|
|
2017-11-03 12:03:00 +00:00
|
|
|
void TopBar::createSearchView(
|
2017-11-03 18:26:14 +00:00
|
|
|
not_null<Ui::SearchFieldController*> controller,
|
2017-12-08 08:14:30 +00:00
|
|
|
rpl::producer<bool> &&shown,
|
|
|
|
bool startsFocused) {
|
2017-11-03 18:26:14 +00:00
|
|
|
setSearchField(
|
|
|
|
controller->createField(this, _st.searchRow.field),
|
2017-12-08 08:14:30 +00:00
|
|
|
std::move(shown),
|
|
|
|
startsFocused);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool TopBar::focusSearchField() {
|
|
|
|
if (_searchField && _searchField->isVisible()) {
|
|
|
|
_searchField->setFocus();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
2017-11-03 12:03:00 +00:00
|
|
|
}
|
|
|
|
|
2017-11-27 11:43:57 +00:00
|
|
|
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;
|
|
|
|
});
|
2017-12-07 15:01:41 +00:00
|
|
|
weak->toggle(
|
|
|
|
!selectionMode() && !_searchModeEnabled,
|
|
|
|
anim::type::instant);
|
2017-12-22 07:05:20 +00:00
|
|
|
weak->widthValue(
|
|
|
|
) | rpl::start_with_next([this] {
|
|
|
|
updateControlsGeometry(width());
|
|
|
|
}, lifetime());
|
2017-11-27 11:43:57 +00:00
|
|
|
return weak;
|
2017-09-13 17:01:23 +00:00
|
|
|
}
|
|
|
|
|
2018-09-09 17:38:08 +00:00
|
|
|
void TopBar::forceButtonVisibility(
|
|
|
|
Ui::FadeWrap<Ui::RpWidget> *button,
|
|
|
|
rpl::producer<bool> shown) {
|
|
|
|
_updateControlCallbacks.erase(button);
|
|
|
|
button->toggleOn(std::move(shown));
|
|
|
|
}
|
|
|
|
|
2017-11-03 12:03:00 +00:00
|
|
|
void TopBar::setSearchField(
|
2017-11-03 18:26:14 +00:00
|
|
|
base::unique_qptr<Ui::InputField> field,
|
2017-12-08 08:14:30 +00:00
|
|
|
rpl::producer<bool> &&shown,
|
|
|
|
bool startsFocused) {
|
|
|
|
Expects(field != nullptr);
|
|
|
|
createSearchView(field.release(), std::move(shown), startsFocused);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TopBar::clearSearchField() {
|
|
|
|
_searchView = nullptr;
|
2017-11-03 12:03:00 +00:00
|
|
|
}
|
|
|
|
|
2017-11-03 18:26:14 +00:00
|
|
|
void TopBar::createSearchView(
|
|
|
|
not_null<Ui::InputField*> field,
|
2017-12-08 08:14:30 +00:00
|
|
|
rpl::producer<bool> &&shown,
|
|
|
|
bool startsFocused) {
|
2017-11-03 12:03:00 +00:00
|
|
|
_searchView = base::make_unique_q<Ui::FixedHeightWidget>(
|
|
|
|
this,
|
|
|
|
_st.searchRow.height);
|
|
|
|
auto wrap = _searchView.get();
|
2017-11-27 11:43:57 +00:00
|
|
|
registerUpdateControlCallback(wrap, [=](anim::type) {
|
|
|
|
wrap->setVisible(!selectionMode() && _searchModeAvailable);
|
|
|
|
});
|
2017-11-03 12:03:00 +00:00
|
|
|
|
2017-12-08 08:14:30 +00:00
|
|
|
_searchField = field;
|
2017-11-27 11:43:57 +00:00
|
|
|
auto fieldWrap = Ui::CreateChild<Ui::FadeWrap<Ui::InputField>>(
|
2017-11-03 12:03:00 +00:00
|
|
|
wrap,
|
2017-11-27 11:43:57 +00:00
|
|
|
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()) {
|
2017-11-17 13:23:36 +00:00
|
|
|
setFocus();
|
|
|
|
}
|
2017-11-27 11:43:57 +00:00
|
|
|
fieldWrap->toggle(fieldShown, animated);
|
|
|
|
if (fieldShown) {
|
|
|
|
*focusLifetime = field->shownValue()
|
|
|
|
| rpl::filter([](bool shown) { return shown; })
|
|
|
|
| rpl::take(1)
|
2017-12-22 07:05:20 +00:00
|
|
|
| rpl::start_with_next([=] { field->setFocus(); });
|
2017-11-27 11:43:57 +00:00
|
|
|
} else {
|
|
|
|
focusLifetime->destroy();
|
2017-11-17 13:23:36 +00:00
|
|
|
}
|
2017-11-27 11:43:57 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
auto button = base::make_unique_q<Ui::IconButton>(this, _st.search);
|
|
|
|
auto search = button.get();
|
2019-03-09 18:44:46 +00:00
|
|
|
search->addClickHandler([=] { showSearch(); });
|
2017-11-27 11:43:57 +00:00
|
|
|
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();
|
|
|
|
});
|
2017-11-03 12:03:00 +00:00
|
|
|
|
|
|
|
auto cancelSearch = [=] {
|
|
|
|
if (!field->getLastText().isEmpty()) {
|
|
|
|
field->setText(QString());
|
|
|
|
} else {
|
2017-11-27 11:43:57 +00:00
|
|
|
_searchModeEnabled = false;
|
|
|
|
updateControlsVisibility(anim::type::normal);
|
2017-11-03 12:03:00 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2017-11-27 11:43:57 +00:00
|
|
|
cancel->addClickHandler(cancelSearch);
|
2023-08-31 11:21:24 +00:00
|
|
|
field->cancelled(
|
|
|
|
) | rpl::start_with_next(cancelSearch, field->lifetime());
|
2017-11-03 12:03:00 +00:00
|
|
|
|
2017-12-22 07:05:20 +00:00
|
|
|
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());
|
2017-11-03 12:03:00 +00:00
|
|
|
|
2017-12-08 08:14:30 +00:00
|
|
|
_searchModeEnabled = !field->getLastText().isEmpty() || startsFocused;
|
2017-11-27 11:43:57 +00:00
|
|
|
updateControlsVisibility(anim::type::instant);
|
2017-11-03 18:26:14 +00:00
|
|
|
|
2017-12-22 07:05:20 +00:00
|
|
|
std::move(
|
|
|
|
shown
|
|
|
|
) | rpl::start_with_next([=](bool visible) {
|
|
|
|
auto alreadyInSearch = !field->getLastText().isEmpty();
|
|
|
|
_searchModeAvailable = visible || alreadyInSearch;
|
|
|
|
updateControlsVisibility(anim::type::instant);
|
|
|
|
}, wrap->lifetime());
|
2017-11-03 12:03:00 +00:00
|
|
|
}
|
|
|
|
|
2019-03-09 18:44:46 +00:00
|
|
|
void TopBar::showSearch() {
|
|
|
|
_searchModeEnabled = true;
|
|
|
|
updateControlsVisibility(anim::type::normal);
|
|
|
|
}
|
|
|
|
|
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());
|
|
|
|
}
|
|
|
|
|
2017-09-13 17:01:23 +00:00
|
|
|
int TopBar::resizeGetHeight(int newWidth) {
|
|
|
|
updateControlsGeometry(newWidth);
|
|
|
|
return _st.height;
|
|
|
|
}
|
|
|
|
|
|
|
|
void TopBar::updateControlsGeometry(int newWidth) {
|
2017-11-26 17:05:52 +00:00
|
|
|
updateDefaultControlsGeometry(newWidth);
|
|
|
|
updateSelectionControlsGeometry(newWidth);
|
2023-06-20 15:31:40 +00:00
|
|
|
updateStoriesGeometry(newWidth);
|
2017-11-26 17:05:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void TopBar::updateDefaultControlsGeometry(int newWidth) {
|
2017-09-13 17:01:23 +00:00
|
|
|
auto right = 0;
|
|
|
|
for (auto &button : _buttons) {
|
2023-06-20 15:31:40 +00:00
|
|
|
if (!button) {
|
|
|
|
continue;
|
|
|
|
}
|
2017-09-13 17:01:23 +00:00
|
|
|
button->moveToRight(right, 0, newWidth);
|
|
|
|
right += button->width();
|
|
|
|
}
|
|
|
|
if (_back) {
|
2017-09-15 17:34:41 +00:00
|
|
|
_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-09-13 17:01:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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();
|
|
|
|
}
|
2023-06-30 12:45:41 +00:00
|
|
|
if (_canToggleStoryPin) {
|
|
|
|
_toggleStoryPin->moveToRight(right, 0, newWidth);
|
|
|
|
right += _toggleStoryPin->width();
|
|
|
|
}
|
2021-11-30 12:26:47 +00:00
|
|
|
if (_canForward) {
|
|
|
|
_forward->moveToRight(right, 0, newWidth);
|
|
|
|
right += _forward->width();
|
|
|
|
}
|
2017-11-26 17:05:52 +00:00
|
|
|
|
|
|
|
auto left = 0;
|
|
|
|
_cancelSelection->moveToLeft(left, 0);
|
|
|
|
left += _cancelSelection->width();
|
|
|
|
|
|
|
|
const auto top = 0;
|
|
|
|
const auto availableWidth = newWidth - left - right;
|
2017-11-27 11:43:57 +00:00
|
|
|
_selectionText->resizeToNaturalWidth(availableWidth);
|
2017-11-26 17:05:52 +00:00
|
|
|
_selectionText->moveToLeft(
|
|
|
|
left,
|
|
|
|
top,
|
|
|
|
newWidth);
|
|
|
|
}
|
|
|
|
|
2023-06-20 15:31:40 +00:00
|
|
|
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();
|
|
|
|
}
|
2023-07-13 08:39:18 +00:00
|
|
|
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);
|
2023-06-20 15:31:40 +00:00
|
|
|
}
|
|
|
|
|
2017-09-13 17:01:23 +00:00
|
|
|
void TopBar::paintEvent(QPaintEvent *e) {
|
2022-09-16 20:23:27 +00:00
|
|
|
auto p = QPainter(this);
|
2017-11-16 09:13:17 +00:00
|
|
|
|
2019-04-02 09:13:30 +00:00
|
|
|
auto highlight = _a_highlight.value(_highlight ? 1. : 0.);
|
2017-11-16 09:13:17 +00:00
|
|
|
if (_highlight && !_a_highlight.animating()) {
|
|
|
|
_highlight = false;
|
|
|
|
startHighlightAnimation();
|
|
|
|
}
|
2022-02-08 17:25:24 +00:00
|
|
|
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);
|
|
|
|
}
|
2017-11-16 09:13:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void TopBar::highlight() {
|
|
|
|
_highlight = true;
|
|
|
|
startHighlightAnimation();
|
|
|
|
}
|
|
|
|
|
|
|
|
void TopBar::startHighlightAnimation() {
|
|
|
|
_a_highlight.start(
|
|
|
|
[this] { update(); },
|
|
|
|
_highlight ? 0. : 1.,
|
|
|
|
_highlight ? 1. : 0.,
|
|
|
|
_st.highlightDuration);
|
2017-09-13 17:01:23 +00:00
|
|
|
}
|
|
|
|
|
2017-11-27 11:43:57 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-20 15:31:40 +00:00
|
|
|
void TopBar::setStories(rpl::producer<Dialogs::Stories::Content> content) {
|
|
|
|
_storiesLifetime.destroy();
|
2023-07-13 08:39:18 +00:00
|
|
|
delete _storiesWrap.data();
|
2023-06-20 15:31:40 +00:00
|
|
|
if (content) {
|
|
|
|
using namespace Dialogs::Stories;
|
|
|
|
|
|
|
|
auto last = std::move(
|
|
|
|
content
|
|
|
|
) | rpl::start_spawning(_storiesLifetime);
|
2023-07-13 08:39:18 +00:00
|
|
|
|
|
|
|
_storiesWrap = _storiesLifetime.make_state<
|
|
|
|
Ui::FadeWrap<Ui::AbstractButton>
|
|
|
|
>(this, object_ptr<Ui::AbstractButton>(this), st::infoTopBarScale);
|
2023-06-20 15:31:40 +00:00
|
|
|
registerToggleControlCallback(
|
2023-07-13 08:39:18 +00:00
|
|
|
_storiesWrap.data(),
|
2023-06-20 15:31:40 +00:00
|
|
|
[this] { return _storiesCount > 0; });
|
2023-07-13 08:39:18 +00:00
|
|
|
_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());
|
|
|
|
|
2023-06-20 15:31:40 +00:00
|
|
|
_stories = stories;
|
2023-07-13 08:39:18 +00:00
|
|
|
_stories->clicks(
|
2023-06-20 15:31:40 +00:00
|
|
|
) | rpl::start_to_stream(_storyClicks, _stories->lifetime());
|
2023-07-13 08:39:18 +00:00
|
|
|
|
|
|
|
button->setClickedCallback([=] {
|
|
|
|
_storyClicks.fire({});
|
|
|
|
});
|
2023-06-20 15:31:40 +00:00
|
|
|
|
|
|
|
rpl::duplicate(
|
|
|
|
last
|
|
|
|
) | rpl::start_with_next([=](const Content &content) {
|
2023-09-26 08:12:33 +00:00
|
|
|
const auto count = content.total;
|
2023-06-20 15:31:40 +00:00
|
|
|
if (_storiesCount != count) {
|
|
|
|
const auto was = (_storiesCount > 0);
|
|
|
|
_storiesCount = count;
|
|
|
|
const auto now = (_storiesCount > 0);
|
|
|
|
if (was != now) {
|
|
|
|
updateControlsVisibility(anim::type::normal);
|
|
|
|
}
|
2023-07-13 08:39:18 +00:00
|
|
|
if (now) {
|
|
|
|
label->setText(
|
|
|
|
tr::lng_contacts_stories_status(
|
|
|
|
tr::now,
|
|
|
|
lt_count,
|
|
|
|
_storiesCount));
|
|
|
|
}
|
2023-06-20 15:31:40 +00:00
|
|
|
updateControlsGeometry(width());
|
|
|
|
}
|
|
|
|
}, _storiesLifetime);
|
2023-07-13 08:39:18 +00:00
|
|
|
|
|
|
|
_storiesLifetime.add([weak = QPointer<QWidget>(label)] {
|
|
|
|
delete weak.data();
|
|
|
|
});
|
2023-06-20 15:31:40 +00:00
|
|
|
} else {
|
|
|
|
_storiesCount = 0;
|
|
|
|
}
|
|
|
|
updateControlsVisibility(anim::type::instant);
|
|
|
|
}
|
|
|
|
|
2023-06-30 12:45:41 +00:00
|
|
|
void TopBar::setStoriesArchive(bool archive) {
|
|
|
|
_storiesArchive = archive;
|
|
|
|
}
|
|
|
|
|
2017-11-26 17:05:52 +00:00
|
|
|
void TopBar::setSelectedItems(SelectedItems &&items) {
|
2017-11-27 11:43:57 +00:00
|
|
|
auto wasSelectionMode = selectionMode();
|
2017-11-26 17:05:52 +00:00
|
|
|
_selectedItems = std::move(items);
|
|
|
|
if (selectionMode()) {
|
|
|
|
if (_selectionText) {
|
|
|
|
updateSelectionState();
|
2017-11-27 11:43:57 +00:00
|
|
|
if (!wasSelectionMode) {
|
|
|
|
_selectionText->entity()->finishAnimating();
|
|
|
|
}
|
2017-11-26 17:05:52 +00:00
|
|
|
} else {
|
|
|
|
createSelectionControls();
|
|
|
|
}
|
|
|
|
}
|
2017-11-27 11:43:57 +00:00
|
|
|
updateControlsVisibility(anim::type::normal);
|
2017-11-26 17:05:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
SelectedItems TopBar::takeSelectedItems() {
|
|
|
|
_canDelete = false;
|
2021-11-30 12:26:47 +00:00
|
|
|
_canForward = false;
|
2017-11-26 17:05:52 +00:00
|
|
|
return std::move(_selectedItems);
|
|
|
|
}
|
|
|
|
|
2022-02-27 11:14:39 +00:00
|
|
|
rpl::producer<SelectionAction> TopBar::selectionActionRequests() const {
|
|
|
|
return _selectionActionRequests.events();
|
2017-11-26 17:05:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void TopBar::updateSelectionState() {
|
2023-06-30 12:45:41 +00:00
|
|
|
Expects(_selectionText && _delete && _forward && _toggleStoryPin);
|
2017-11-26 17:05:52 +00:00
|
|
|
|
|
|
|
_canDelete = computeCanDelete();
|
2021-11-30 12:26:47 +00:00
|
|
|
_canForward = computeCanForward();
|
2017-11-26 17:05:52 +00:00
|
|
|
_selectionText->entity()->setValue(generateSelectedText());
|
2017-11-28 15:16:28 +00:00
|
|
|
_delete->toggle(_canDelete, anim::type::instant);
|
2021-11-30 12:26:47 +00:00
|
|
|
_forward->toggle(_canForward, anim::type::instant);
|
2023-06-30 12:45:41 +00:00
|
|
|
_toggleStoryPin->toggle(_canToggleStoryPin, anim::type::instant);
|
2017-11-26 17:05:52 +00:00
|
|
|
|
|
|
|
updateSelectionControlsGeometry(width());
|
|
|
|
}
|
|
|
|
|
|
|
|
void TopBar::createSelectionControls() {
|
|
|
|
auto wrap = [&](auto created) {
|
2017-11-27 11:43:57 +00:00
|
|
|
registerToggleControlCallback(
|
|
|
|
created,
|
|
|
|
[this] { return selectionMode(); });
|
2017-11-26 17:05:52 +00:00
|
|
|
created->toggle(false, anim::type::instant);
|
|
|
|
return created;
|
|
|
|
};
|
|
|
|
_canDelete = computeCanDelete();
|
2021-11-30 12:26:47 +00:00
|
|
|
_canForward = computeCanForward();
|
2023-06-30 12:45:41 +00:00
|
|
|
_canToggleStoryPin = computeCanToggleStoryPin();
|
2017-11-27 11:43:57 +00:00
|
|
|
_cancelSelection = wrap(Ui::CreateChild<Ui::FadeWrap<Ui::IconButton>>(
|
2017-11-26 17:05:52 +00:00
|
|
|
this,
|
2017-11-27 11:43:57 +00:00
|
|
|
object_ptr<Ui::IconButton>(this, _st.mediaCancel),
|
|
|
|
st::infoTopBarScale));
|
|
|
|
_cancelSelection->setDuration(st::infoTopBarDuration);
|
2017-12-22 07:05:20 +00:00
|
|
|
_cancelSelection->entity()->clicks(
|
2022-02-27 11:14:39 +00:00
|
|
|
) | rpl::map_to(
|
|
|
|
SelectionAction::Clear
|
|
|
|
) | rpl::start_to_stream(
|
|
|
|
_selectionActionRequests,
|
2017-12-22 07:05:20 +00:00
|
|
|
_cancelSelection->lifetime());
|
2017-11-27 11:43:57 +00:00
|
|
|
_selectionText = wrap(Ui::CreateChild<Ui::FadeWrap<Ui::LabelWithNumbers>>(
|
2017-11-26 17:05:52 +00:00
|
|
|
this,
|
|
|
|
object_ptr<Ui::LabelWithNumbers>(
|
|
|
|
this,
|
|
|
|
_st.title,
|
|
|
|
_st.titlePosition.y(),
|
2017-11-27 11:43:57 +00:00
|
|
|
generateSelectedText()),
|
|
|
|
st::infoTopBarScale));
|
|
|
|
_selectionText->setDuration(st::infoTopBarDuration);
|
2017-11-26 17:05:52 +00:00
|
|
|
_selectionText->entity()->resize(0, _st.height);
|
2017-11-27 11:43:57 +00:00
|
|
|
_forward = wrap(Ui::CreateChild<Ui::FadeWrap<Ui::IconButton>>(
|
2017-11-26 17:05:52 +00:00
|
|
|
this,
|
2017-11-27 11:43:57 +00:00
|
|
|
object_ptr<Ui::IconButton>(this, _st.mediaForward),
|
|
|
|
st::infoTopBarScale));
|
2021-11-30 12:26:47 +00:00
|
|
|
registerToggleControlCallback(
|
|
|
|
_forward.data(),
|
|
|
|
[this] { return selectionMode() && _canForward; });
|
2017-11-27 11:43:57 +00:00
|
|
|
_forward->setDuration(st::infoTopBarDuration);
|
2022-02-27 11:14:39 +00:00
|
|
|
_forward->entity()->clicks(
|
|
|
|
) | rpl::map_to(
|
|
|
|
SelectionAction::Forward
|
|
|
|
) | rpl::start_to_stream(
|
|
|
|
_selectionActionRequests,
|
|
|
|
_cancelSelection->lifetime());
|
2021-11-30 12:26:47 +00:00
|
|
|
_forward->entity()->setVisible(_canForward);
|
2017-11-27 11:43:57 +00:00
|
|
|
_delete = wrap(Ui::CreateChild<Ui::FadeWrap<Ui::IconButton>>(
|
2017-11-26 17:05:52 +00:00
|
|
|
this,
|
2017-11-27 11:43:57 +00:00
|
|
|
object_ptr<Ui::IconButton>(this, _st.mediaDelete),
|
|
|
|
st::infoTopBarScale));
|
2017-11-28 15:16:28 +00:00
|
|
|
registerToggleControlCallback(
|
|
|
|
_delete.data(),
|
|
|
|
[this] { return selectionMode() && _canDelete; });
|
2017-11-27 11:43:57 +00:00
|
|
|
_delete->setDuration(st::infoTopBarDuration);
|
2022-02-27 11:14:39 +00:00
|
|
|
_delete->entity()->clicks(
|
|
|
|
) | rpl::map_to(
|
|
|
|
SelectionAction::Delete
|
|
|
|
) | rpl::start_to_stream(
|
|
|
|
_selectionActionRequests,
|
|
|
|
_cancelSelection->lifetime());
|
2017-11-26 17:05:52 +00:00
|
|
|
_delete->entity()->setVisible(_canDelete);
|
2023-06-30 12:45:41 +00:00
|
|
|
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);
|
2017-11-26 17:05:52 +00:00
|
|
|
|
|
|
|
updateControlsGeometry(width());
|
|
|
|
}
|
|
|
|
|
|
|
|
bool TopBar::computeCanDelete() const {
|
2020-05-18 19:33:14 +00:00
|
|
|
return ranges::all_of(_selectedItems.list, &SelectedItem::canDelete);
|
2017-11-26 17:05:52 +00:00
|
|
|
}
|
|
|
|
|
2021-11-30 12:26:47 +00:00
|
|
|
bool TopBar::computeCanForward() const {
|
|
|
|
return ranges::all_of(_selectedItems.list, &SelectedItem::canForward);
|
|
|
|
}
|
|
|
|
|
2023-06-30 12:45:41 +00:00
|
|
|
bool TopBar::computeCanToggleStoryPin() const {
|
|
|
|
return ranges::all_of(
|
|
|
|
_selectedItems.list,
|
|
|
|
&SelectedItem::canToggleStoryPin);
|
|
|
|
}
|
|
|
|
|
2017-11-26 17:05:52 +00:00
|
|
|
Ui::StringWithNumbers TopBar::generateSelectedText() const {
|
|
|
|
using Type = Storage::SharedMediaType;
|
2019-06-12 20:11:41 +00:00
|
|
|
const auto phrase = [&] {
|
2017-11-26 17:05:52 +00:00
|
|
|
switch (_selectedItems.type) {
|
2019-06-19 12:41:00 +00:00
|
|
|
case Type::Photo: return tr::lng_media_selected_photo;
|
2020-08-23 19:42:28 +00:00
|
|
|
case Type::GIF: return tr::lng_media_selected_gif;
|
2019-06-19 12:41:00 +00:00
|
|
|
case Type::Video: return tr::lng_media_selected_video;
|
|
|
|
case Type::File: return tr::lng_media_selected_file;
|
|
|
|
case Type::MusicFile: return tr::lng_media_selected_song;
|
|
|
|
case Type::Link: return tr::lng_media_selected_link;
|
|
|
|
case Type::RoundVoiceFile: return tr::lng_media_selected_audio;
|
2023-06-30 12:45:41 +00:00
|
|
|
case Type::PhotoVideo: return tr::lng_stories_row_count;
|
2017-11-26 17:05:52 +00:00
|
|
|
}
|
2017-11-27 11:43:57 +00:00
|
|
|
Unexpected("Type in TopBar::generateSelectedText()");
|
2017-11-26 17:05:52 +00:00
|
|
|
}();
|
2019-06-19 12:41:00 +00:00
|
|
|
return phrase(
|
|
|
|
tr::now,
|
|
|
|
lt_count,
|
|
|
|
_selectedItems.list.size(),
|
|
|
|
Ui::StringWithNumbers::FromString);
|
2017-11-26 17:05:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool TopBar::selectionMode() const {
|
|
|
|
return !_selectedItems.list.empty();
|
|
|
|
}
|
|
|
|
|
2023-06-20 15:31:40 +00:00
|
|
|
bool TopBar::storiesTitle() const {
|
|
|
|
return _storiesCount > 0;
|
|
|
|
}
|
|
|
|
|
2017-11-27 11:43:57 +00:00
|
|
|
bool TopBar::searchMode() const {
|
|
|
|
return _searchModeAvailable && _searchModeEnabled;
|
2017-11-26 17:05:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void TopBar::performForward() {
|
2022-02-27 11:14:39 +00:00
|
|
|
_selectionActionRequests.fire(SelectionAction::Forward);
|
2017-11-26 17:05:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void TopBar::performDelete() {
|
2022-02-27 11:14:39 +00:00
|
|
|
_selectionActionRequests.fire(SelectionAction::Delete);
|
2017-11-26 17:05:52 +00:00
|
|
|
}
|
|
|
|
|
2017-09-13 17:01:23 +00:00
|
|
|
} // namespace Info
|