From c8218b3b685dec216d113bee8bf0eb7db31f41c3 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 7 Jul 2026 01:09:52 +0400 Subject: [PATCH] Add search bar to the markdown IV window --- Telegram/SourceFiles/iv/iv.style | 2 + Telegram/SourceFiles/iv/iv_search_bar.cpp | 200 ++++++++++++++++++ Telegram/SourceFiles/iv/iv_search_bar.h | 62 ++++++ .../iv/markdown/iv_markdown_controller.cpp | 49 ++++- .../iv/markdown/iv_markdown_controller.h | 8 + Telegram/cmake/td_iv.cmake | 2 + 6 files changed, 322 insertions(+), 1 deletion(-) create mode 100644 Telegram/SourceFiles/iv/iv_search_bar.cpp create mode 100644 Telegram/SourceFiles/iv/iv_search_bar.h diff --git a/Telegram/SourceFiles/iv/iv.style b/Telegram/SourceFiles/iv/iv.style index ff6a7e6330..d0591a014c 100644 --- a/Telegram/SourceFiles/iv/iv.style +++ b/Telegram/SourceFiles/iv/iv.style @@ -73,6 +73,8 @@ ivSubtitleHeight: 48px; ivSubtitleTop: 12px; ivSubtitleLeft: 22px; ivSubtitleSkip: 0px; +ivSearchBarHeight: ivSubtitleHeight; +ivSearchBarCounterSkip: 10px; ivWrongLayoutBarHeight: 32px; ivWrongLayoutLink: LinkButton(defaultLinkButton) { color: windowSubTextFg; diff --git a/Telegram/SourceFiles/iv/iv_search_bar.cpp b/Telegram/SourceFiles/iv/iv_search_bar.cpp new file mode 100644 index 0000000000..89ea906495 --- /dev/null +++ b/Telegram/SourceFiles/iv/iv_search_bar.cpp @@ -0,0 +1,200 @@ +/* +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 "iv/iv_search_bar.h" + +#include "lang/lang_keys.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/labels.h" +#include "ui/widgets/multi_select.h" + +#include "styles/palette.h" +#include "styles/style_boxes.h" +#include "styles/style_chat.h" +#include "styles/style_iv.h" +#include "styles/style_widgets.h" + +#include + +namespace Iv { + +SearchBar::SearchBar(not_null parent) +: _wrap(parent, object_ptr(parent.get())) +, _shadow(parent) { + setup(parent); + _wrap.hide(anim::type::instant); + _shadow.hide(); +} + +void SearchBar::setup(not_null parent) { + const auto inner = _wrap.entity(); + inner->resize(inner->width(), st::ivSearchBarHeight); + + _select = Ui::CreateChild( + inner, + st::searchInChatMultiSelect, + tr::lng_dlg_filter()); + _counter = Ui::CreateChild( + inner, + QString(), + st::defaultSettingsRightLabel); + _counter->setAttribute(Qt::WA_TransparentForMouseEvents); + _counter->hide(); + _up = Ui::CreateChild(inner, st::calendarPrevious); + _down = Ui::CreateChild(inner, st::calendarNext); + _close = Ui::CreateChild( + inner, + st::defaultMultiSelectSearchCancel); + _close->show(anim::type::instant); + + inner->paintRequest( + ) | rpl::on_next([=](QRect clip) { + QPainter(inner).fillRect(clip, st::windowBg); + }, inner->lifetime()); + + parent->widthValue( + ) | rpl::on_next([=](int width) { + _wrap.resizeToWidth(width); + }, _wrap.lifetime()); + + inner->sizeValue( + ) | rpl::on_next([=] { + updateControlsGeometry(); + }, inner->lifetime()); + + _wrap.geometryValue( + ) | rpl::on_next([=](QRect geometry) { + _shadow.setGeometry( + geometry.x(), + geometry.y() + geometry.height(), + geometry.width(), + st::lineWidth); + }, _shadow.lifetime()); + + _shadow.showOn(rpl::combine( + _wrap.shownValue(), + _wrap.heightValue(), + rpl::mappers::_1 && rpl::mappers::_2 > 0 + ) | rpl::filter([=](bool shown) { + return (shown == _shadow.isHidden()); + })); + + _select->setQueryChangedCallback([=](const QString &query) { + _queryChanges.fire_copy(query); + }); + _select->setSubmittedCallback([=](Qt::KeyboardModifiers) { + _submits.fire({}); + }); + _select->setCancelledCallback([=] { + _closeRequests.fire({}); + }); + + setResults(0, 0); +} + +void SearchBar::updateControlsGeometry() { + const auto inner = _wrap.entity(); + const auto width = inner->width(); + const auto height = inner->height(); + _close->moveToRight(0, (height - _close->height()) / 2); + _down->moveToRight(_close->width(), (height - _down->height()) / 2); + _up->moveToRight( + _close->width() + _down->width(), + (height - _up->height()) / 2); + auto right = _close->width() + _down->width() + _up->width(); + if (!_counter->isHidden()) { + right += st::ivSearchBarCounterSkip; + _counter->moveToRight(right, (height - _counter->height()) / 2); + right += _counter->width() + st::ivSearchBarCounterSkip; + } + _select->resizeToWidth(width - right); + _select->moveToLeft(0, (height - _select->height()) / 2); +} + +void SearchBar::setResults(int current, int total) { + if (total > 0) { + _counter->setText(tr::lng_search_messages_n_of_amount( + tr::now, + lt_n, + QString::number(current), + lt_amount, + QString::number(total))); + _counter->show(); + } else { + _counter->hide(); + } + const auto upDisabled = (total <= 0) || (current <= 1); + const auto downDisabled = (total <= 0) || (current >= total); + _up->setIconOverride(upDisabled + ? &st::calendarPreviousDisabled + : nullptr); + _down->setIconOverride(downDisabled + ? &st::calendarNextDisabled + : nullptr); + _up->setAttribute(Qt::WA_TransparentForMouseEvents, upDisabled); + _down->setAttribute(Qt::WA_TransparentForMouseEvents, downDisabled); + updateControlsGeometry(); +} + +void SearchBar::toggle(bool shown, anim::type animated) { + _wrap.toggle(shown, animated); +} + +void SearchBar::show(anim::type animated) { + toggle(true, animated); +} + +void SearchBar::hide(anim::type animated) { + toggle(false, animated); +} + +bool SearchBar::shown() const { + return _wrap.toggled(); +} + +void SearchBar::setInnerFocus() { + _select->setInnerFocus(); +} + +void SearchBar::raise() { + _wrap.raise(); + _shadow.raise(); +} + +void SearchBar::move(int x, int y) { + _wrap.move(x, y); +} + +rpl::producer SearchBar::queryChanges() const { + return _queryChanges.events(); +} + +rpl::producer<> SearchBar::submits() const { + return _submits.events(); +} + +rpl::producer SearchBar::navigateRequests() const { + return rpl::merge( + _up->clicks() | rpl::map_to(-1), + _down->clicks() | rpl::map_to(1)); +} + +rpl::producer<> SearchBar::closeRequests() const { + return rpl::merge( + _closeRequests.events(), + _close->clicks() | rpl::to_empty); +} + +rpl::producer SearchBar::heightValue() const { + return _wrap.heightValue(); +} + +rpl::lifetime &SearchBar::lifetime() { + return _wrap.lifetime(); +} + +} // namespace Iv diff --git a/Telegram/SourceFiles/iv/iv_search_bar.h b/Telegram/SourceFiles/iv/iv_search_bar.h new file mode 100644 index 0000000000..d205d29723 --- /dev/null +++ b/Telegram/SourceFiles/iv/iv_search_bar.h @@ -0,0 +1,62 @@ +/* +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 + +#include "base/basic_types.h" +#include "ui/widgets/shadow.h" +#include "ui/wrap/slide_wrap.h" + +namespace Ui { +class CrossButton; +class FlatLabel; +class IconButton; +class MultiSelect; +} // namespace Ui + +namespace Iv { + +class SearchBar final { +public: + explicit SearchBar(not_null parent); + + void toggle(bool shown, anim::type animated); + void show(anim::type animated); + void hide(anim::type animated); + [[nodiscard]] bool shown() const; + void setInnerFocus(); + void raise(); + void move(int x, int y); + + void setResults(int current, int total); + + [[nodiscard]] rpl::producer queryChanges() const; + [[nodiscard]] rpl::producer<> submits() const; + [[nodiscard]] rpl::producer navigateRequests() const; + [[nodiscard]] rpl::producer<> closeRequests() const; + [[nodiscard]] rpl::producer heightValue() const; + + [[nodiscard]] rpl::lifetime &lifetime(); + +private: + void setup(not_null parent); + void updateControlsGeometry(); + + Ui::SlideWrap _wrap; + Ui::PlainShadow _shadow; + Ui::MultiSelect *_select = nullptr; + Ui::FlatLabel *_counter = nullptr; + Ui::IconButton *_up = nullptr; + Ui::IconButton *_down = nullptr; + Ui::CrossButton *_close = nullptr; + rpl::event_stream _queryChanges; + rpl::event_stream<> _submits; + rpl::event_stream<> _closeRequests; + +}; + +} // namespace Iv diff --git a/Telegram/SourceFiles/iv/markdown/iv_markdown_controller.cpp b/Telegram/SourceFiles/iv/markdown/iv_markdown_controller.cpp index 18c71ed690..0dd053d80b 100644 --- a/Telegram/SourceFiles/iv/markdown/iv_markdown_controller.cpp +++ b/Telegram/SourceFiles/iv/markdown/iv_markdown_controller.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "iv/markdown/iv_markdown_controller.h" #include "base/event_filter.h" +#include "base/invoke_queued.h" #include "base/weak_ptr.h" #include "core/click_handler_types.h" #include "core/credits_amount.h" @@ -14,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "iv/markdown/iv_markdown_parse.h" #include "iv/markdown/iv_markdown_view.h" #include "iv/iv_delegate_impl.h" +#include "iv/iv_search_bar.h" #include "iv/iv_zoom_controls.h" #include "lang/lang_keys.h" #include "ui/layers/layer_manager.h" @@ -1022,6 +1024,41 @@ void Controller::createPreview() { updateHistoryButtons(); } +void Controller::createSearchBar() { + _searchBar = std::make_unique(_window->body().get()); + _searchBar->move(0, st::ivSubtitleHeight); + _searchBar->raise(); + _titleShadow->raise(); + _searchBar->closeRequests() | rpl::on_next([=] { + InvokeQueued(_window->body().get(), [=] { + hideSearchBar(); + }); + }, _searchBar->lifetime()); +} + +void Controller::toggleSearchBar() { + if (!_window) { + return; + } else if (_searchBar && _searchBar->shown()) { + hideSearchBar(); + return; + } + if (!_searchBar) { + createSearchBar(); + } + _searchBar->show(anim::type::normal); + _searchBar->setInnerFocus(); +} + +void Controller::hideSearchBar() { + if (_searchBar) { + _searchBar->hide(anim::type::normal); + } + if (_preview) { + _preview->setFocus(); + } +} + void Controller::createWindow() { _window = std::make_unique(); const auto window = _window.get(); @@ -1111,7 +1148,11 @@ void Controller::createWindow() { const auto event = static_cast(e.get()); if (event->key() == Qt::Key_Escape) { event->accept(); - close(); + if (_searchBar && _searchBar->shown()) { + hideSearchBar(); + } else { + close(); + } } } }, window->lifetime()); @@ -1122,6 +1163,12 @@ void Controller::createWindow() { const auto event = static_cast(e.get()); const auto previousAccepted = event->isAccepted(); ProcessZoomShortcut(_delegate, event); + if (!event->isAccepted() + && (event->modifiers() & Qt::ControlModifier) + && event->key() == Qt::Key_F) { + event->accept(); + toggleSearchBar(); + } return event->isAccepted() && !previousAccepted ? base::EventFilterResult::Cancel : base::EventFilterResult::Continue; diff --git a/Telegram/SourceFiles/iv/markdown/iv_markdown_controller.h b/Telegram/SourceFiles/iv/markdown/iv_markdown_controller.h index 9dc759837f..b8e4093113 100644 --- a/Telegram/SourceFiles/iv/markdown/iv_markdown_controller.h +++ b/Telegram/SourceFiles/iv/markdown/iv_markdown_controller.h @@ -29,6 +29,10 @@ template class FadeWrapScaled; } // namespace Ui +namespace Iv { +class SearchBar; +} // namespace Iv + namespace Iv::Markdown { class Controller final { @@ -85,6 +89,9 @@ private: bool preserveScroll); void updateTitleGeometry(int newWidth) const; void showMenu(); + void createSearchBar(); + void toggleSearchBar(); + void hideSearchBar(); void openSource(); [[nodiscard]] ViewerKind viewerKind() const; [[nodiscard]] QString subtitleText() const; @@ -154,6 +161,7 @@ private: std::unique_ptr _layerManager; std::shared_ptr _show; std::unique_ptr _preview; + std::unique_ptr _searchBar; std::vector _history; int _historyIndex = -1; int _shownHistoryIndex = -1; diff --git a/Telegram/cmake/td_iv.cmake b/Telegram/cmake/td_iv.cmake index 51c3149139..c3a661bdea 100644 --- a/Telegram/cmake/td_iv.cmake +++ b/Telegram/cmake/td_iv.cmake @@ -32,6 +32,8 @@ PRIVATE iv/iv_data.h iv/iv_delegate.h iv/iv_pch.h + iv/iv_search_bar.cpp + iv/iv_search_bar.h iv/iv_zoom_controls.cpp iv/iv_zoom_controls.h )