diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 13af508c3..f8344b94b 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -1537,6 +1537,8 @@ PRIVATE ui/widgets/expandable_peer_list.h ui/widgets/label_with_custom_emoji.cpp ui/widgets/label_with_custom_emoji.h + ui/widgets/chat_filters_tabs_strip.cpp + ui/widgets/chat_filters_tabs_strip.h ui/countryinput.cpp ui/countryinput.h ui/dynamic_thumbnails.cpp diff --git a/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_strip.cpp b/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_strip.cpp new file mode 100644 index 000000000..a89e9405a --- /dev/null +++ b/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_strip.cpp @@ -0,0 +1,195 @@ +/* +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 "ui/widgets/chat_filters_tabs_strip.h" + +#include "data/data_chat_filters.h" +#include "data/data_session.h" +#include "lang/lang_keys.h" +#include "main/main_session.h" +#include "ui/widgets/discrete_sliders.h" +#include "ui/widgets/scroll_area.h" +#include "ui/wrap/slide_wrap.h" +#include "styles/style_dialogs.h" // dialogsSearchTabs + +#include + +namespace Ui { + +not_null AddChatFiltersTabsStrip( + not_null parent, + not_null session, + rpl::producer multiSelectHeightValue, + Fn setAddedTopScrollSkip, + Fn choose) { + class Slider final : public Ui::SettingsSlider { + public: + using Ui::SettingsSlider::SettingsSlider; + + [[nodiscard]] int centerOfSection(int section) const { + const auto widths = Ui::SettingsSlider::countSectionsWidths(0); + auto result = 0; + if (section >= 0 && section < widths.size()) { + for (auto i = 0; i < section; i++) { + result += widths[i]; + } + result += widths[section] / 2; + } + return result; + } + + void fitWidthToSections() { + const auto widths = Ui::SettingsSlider::countSectionsWidths(0); + resizeToWidth(ranges::accumulate(widths, .0)); + } + }; + + struct State final { + Ui::Animations::Simple animation; + std::optional lastFilterId = std::nullopt; + }; + + const auto &scrollSt = st::defaultScrollArea; + const auto wrap = Ui::CreateChild>( + parent, + object_ptr(parent)); + const auto container = wrap->entity(); + const auto scroll = Ui::CreateChild(container, scrollSt); + const auto sliderPadding = st::dialogsSearchTabsPadding; + const auto slider = scroll->setOwnedWidget( + object_ptr>( + parent, + object_ptr(parent, st::dialogsSearchTabs), + QMargins(sliderPadding, 0, sliderPadding, 0)))->entity(); + const auto state = wrap->lifetime().make_state(); + wrap->toggle(false, anim::type::instant); + container->sizeValue() | rpl::start_with_next([=](const QSize &s) { + scroll->resize(s + QSize(0, scrollSt.deltax * 4)); + }, scroll->lifetime()); + rpl::combine( + parent->widthValue(), + slider->heightValue() + ) | rpl::start_with_next([=](int w, int h) { + container->resize(w, h); + }, wrap->lifetime()); + scroll->setCustomWheelProcess([=](not_null e) { + const auto pixelDelta = e->pixelDelta(); + const auto angleDelta = e->angleDelta(); + if (std::abs(pixelDelta.x()) + std::abs(angleDelta.x())) { + return false; + } + const auto bar = scroll->horizontalScrollBar(); + const auto y = pixelDelta.y() ? pixelDelta.y() : angleDelta.y(); + bar->setValue(bar->value() - y); + return true; + }); + + const auto scrollToIndex = [=](int index, anim::type type) { + const auto to = index + ? (slider->centerOfSection(index) - scroll->width() / 2) + : 0; + const auto bar = scroll->horizontalScrollBar(); + state->animation.stop(); + if (type == anim::type::instant) { + bar->setValue(to); + } else { + state->animation.start( + [=](float64 v) { bar->setValue(v); }, + bar->value(), + std::min(to, bar->maximum()), + st::defaultTabsSlider.duration); + } + }; + + const auto applyFilter = [=](const Data::ChatFilter &filter) { + choose(filter.id()); + }; + + const auto filterByIndex = [=](int index) -> const Data::ChatFilter& { + const auto &list = session->data().chatsFilters().list(); + Assert(index >= 0 && index < list.size()); + return list[index]; + }; + + const auto rebuild = [=] { + const auto &list = session->data().chatsFilters().list(); + auto sections = ranges::views::all( + list + ) | ranges::views::transform([](const Data::ChatFilter &filter) { + return filter.title().isEmpty() + ? tr::lng_filters_all(tr::now) + : filter.title(); + }) | ranges::to_vector; + slider->setSections(std::move(sections)); + slider->fitWidthToSections(); + [&] { + const auto lookingId = state->lastFilterId.value_or(list[0].id()); + for (auto i = 0; i < list.size(); i++) { + const auto &filter = list[i]; + if (filter.id() == lookingId) { + const auto wasLast = !!state->lastFilterId; + state->lastFilterId = filter.id(); + slider->setActiveSectionFast(i); + scrollToIndex( + i, + wasLast ? anim::type::normal : anim::type::instant); + applyFilter(filter); + return; + } + } + if (list.size()) { + const auto index = 0; + const auto &filter = filterByIndex(index); + state->lastFilterId = filter.id(); + slider->setActiveSectionFast(index); + scrollToIndex(index, anim::type::instant); + applyFilter(filter); + } + }(); + slider->sectionActivated() | rpl::start_with_next([=](int index) { + const auto &filter = filterByIndex(index); + state->lastFilterId = filter.id(); + scrollToIndex(index, anim::type::normal); + applyFilter(filter); + }, wrap->lifetime()); + wrap->toggle((list.size() > 1), anim::type::instant); + }; + session->data().chatsFilters().changed( + ) | rpl::start_with_next(rebuild, wrap->lifetime()); + rebuild(); + + session->data().chatsFilters().isChatlistChanged( + ) | rpl::start_with_next([=](FilterId id) { + if (!id || !state->lastFilterId || (id != state->lastFilterId)) { + return; + } + for (const auto &filter : session->data().chatsFilters().list()) { + if (filter.id() == id) { + applyFilter(filter); + return; + } + } + }, wrap->lifetime()); + + { + std::move( + multiSelectHeightValue + ) | rpl::start_with_next([=](int height) { + wrap->moveToLeft(0, height); + }, wrap->lifetime()); + wrap->heightValue() | rpl::start_with_next([=](int height) { + setAddedTopScrollSkip(height); + }, wrap->lifetime()); + parent->widthValue() | rpl::start_with_next([=](int w) { + wrap->resizeToWidth(w); + }, wrap->lifetime()); + } + + return wrap; +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_strip.h b/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_strip.h new file mode 100644 index 000000000..c50814830 --- /dev/null +++ b/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_strip.h @@ -0,0 +1,27 @@ +/* +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 +*/ +#pragma once + +namespace Main { +class Session; +} // namespace Main + +namespace Ui { +class RpWidget; +} // namespace Ui + +namespace Ui { + +not_null AddChatFiltersTabsStrip( + not_null parent, + not_null session, + rpl::producer multiSelectHeightValue, + Fn setAddedTopScrollSkip, + Fn choose); + +} // namespace Ui diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 00b64a931..b969b5bd3 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 00b64a9311b0fcc4be14dc705a69aec16f6ced5f +Subproject commit b969b5bd32c339be43732d7f1d34d7fdc3454eab